<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Aitozi</title>
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://www.aitozi.com/"/>
  <updated>2020-01-22T12:38:28.081Z</updated>
  <id>http://www.aitozi.com/</id>
  
  <author>
    <name>aitozi</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>理解golang的强静态类型</title>
    <link href="http://www.aitozi.com/golang-strong-static-type.html"/>
    <id>http://www.aitozi.com/golang-strong-static-type.html</id>
    <published>2020-01-22T09:54:03.000Z</published>
    <updated>2020-01-22T12:38:28.081Z</updated>
    
    <content type="html"><![CDATA[<p>more</p>
<a id="more"></a>
<h2 id="理解golang的强，静态类型"><a href="#理解golang的强，静态类型" class="headerlink" title="理解golang的强，静态类型"></a>理解golang的强，静态类型</h2><p>关于弱类型，强类型，动态类型，静态类型语言的区别，在这里补充一下</p>
<h3 id="Program-Errors"><a href="#Program-Errors" class="headerlink" title="Program Errors"></a>Program Errors</h3><ul>
<li><em>trapped errors</em>。导致程序终止执行，如除0，Java中数组越界访问</li>
<li><em>untrapped errors</em>。 出错后继续执行，但可能出现任意行为。如C里的缓冲区溢出、Jump到错误地址</li>
</ul>
<h3 id="Forbidden-Behaviours"><a href="#Forbidden-Behaviours" class="headerlink" title="Forbidden Behaviours"></a>Forbidden Behaviours</h3><p>语言设计时，可以定义一组<em>forbidden behaviors</em>. 它必须包括所有untrapped errors, 但可能包含trapped errors.</p>
<h3 id="Well-behaved、ill-behaved"><a href="#Well-behaved、ill-behaved" class="headerlink" title="Well behaved、ill behaved"></a>Well behaved、ill behaved</h3><ul>
<li><em>well behaved</em>: 如果程序执行不可能出现forbidden behaviors, 则为<em>well behaved</em>。</li>
<li><em>ill behaved: 否则为ill behaved…</em></li>
</ul>
<p>基于上面的概念再讨论强弱类型，静态和动态类型</p>
<h3 id="强弱类型"><a href="#强弱类型" class="headerlink" title="强弱类型"></a>强弱类型</h3><ul>
<li>强类型strongly typed: 如果一种语言的所有程序都是well behaved——即不可能出现forbidden behaviors，则该语言为strongly typed。</li>
<li>弱类型weakly typed: 否则为weakly typed。比如C语言的缓冲区溢出，属于trapped errors，即属于forbidden behaviors..故C是弱类型</li>
</ul>
<h3 id="动态、静态类型"><a href="#动态、静态类型" class="headerlink" title="动态、静态类型"></a>动态、静态类型</h3><ul>
<li>静态类型 statically: 如果在编译时拒绝ill behaved程序，则是statically typed;</li>
<li>动态类型dynamiclly: 如果在运行时拒绝ill behaviors, 则是dynamiclly typed。</li>
</ul>
<h3 id="误区"><a href="#误区" class="headerlink" title="误区"></a>误区</h3><p>大家觉得C语言要写int a, int b之类的，Python不用写(可以直接写a, b)，所以C是静态，Python是动态。这么理解是不够准确的。譬如Ocaml是静态类型的，但是也可以不用明确地写出来。  Ocaml是静态隐式类型</p>
<p>静态类型可以分为两种</p>
<ul>
<li>如果类型是语言语法的一部分，在是explicitly typed显式类型；</li>
<li>如果类型通过编译时推导，是implicity typed隐式类型, 比如ML和Haskell</li>
</ul>
<h3 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h3><p>无类型： 汇编<br>弱类型、静态类型 ： C/C++<br>弱类型、动态类型检查： Perl/PHP<br>强类型、静态类型检查 ：Java/C#<br>强类型、动态类型检查 ：Python, Scheme<br>静态显式类型 ：Java/C<br>静态隐式类型 ：Ocaml, Haskell</p>
<p>简化理解</p>
<ol>
<li>动态类型语言：意思就是类型的检查是在运行时做的。</li>
<li>静态类型语言：类型判断是在运行前，在编译期进行判断。</li>
<li>强类型：一旦一个变量被指定了某个数据类型，如果不经过强制转换，那么它就永远是这个数据类型了。</li>
<li>弱类型：一个变量可以赋不同数据类型的值</li>
</ol>
<p>那么go语言为什么选择的是强静态类型语言的实现呢？</p>
<p>静态类型的优点：</p>
<ol>
<li>IDE聪明的提示，因为静态类型的语言的类型是确定的，所以编辑器可以知道当前的变量有哪些属性和方法。</li>
<li>编译的时候能够发现类型不匹配的错误，而动态语言至多只能发现语法错误。</li>
<li>我们在开发过程中明确了某些变量在程序中扮演了什么角色，这是开发可靠性高的程序所必须的。</li>
</ol>
<p>静态类型的缺点：</p>
<ol>
<li>因为要定义数据类型，程序的规模也变得很大，编程应该考虑程序的本质，而不是把精力集中于一个个数据类型的定义。</li>
<li>缺乏灵活性，因为一个变量,只能赋值某种类型的对象。明显当程序需要扩展的时候，这会成为枷锁，当然可以通过继承和接口实现，这会陷入另一个深渊，你总会去纠结复杂的继承关系。</li>
</ol>
<p>动态类型的优点：</p>
<ol>
<li>相反于静态类型，编程完全集中于程序的设计的本质，代码的简洁度也会提高，开发效率可能会数倍的提高。</li>
<li>因为程序的规模降低， 程序的可理解性也会提高。（静态类型的拥护者可能会认为，少了类型信息，程序变的不可读了。这里认为读程序应该集中在程序的本质上）</li>
</ol>
<p>动态类型的缺点：</p>
<ol>
<li>程序执行速度慢，因为动态类型的语言，类型检查是在运行期做的。(随着计算机性能的提高，执行速度不是什么严重的问题了，关键是生产力提高了)</li>
<li>不执行就检测不出错误。</li>
</ol>
<p>对于开发大型系统，静态强类型语言会优于动态弱类型语言，代码的可读性和可维护性上都会更强，采用静态强类型语言可以避免程序员在动态语言中无意犯下的绝大多数错误。Go 语言将静态语言的安全性和高效性与动态语言的易开发性进行有机结合，达到完美平衡.(GO语言是一门类型安全(强类型)和内存安全的编程语言。)</p>
<p>go支持这样定义变量</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">var abc = 10</span><br></pre></td></tr></table></figure>
<p>让golang看上去有点像动态类型语言，但golang实际上时强类型的，前面的定义会被自动推导出是int类型, 所以Go是一门写起来像动态语言，有着动态语言开发效率的静态语言。</p>
<h3 id="快速构建"><a href="#快速构建" class="headerlink" title="快速构建"></a>快速构建</h3><p>关于golang的快速构建 <a href="https://talks.golang.org/2012/splash.article" target="_blank" rel="noopener">https://talks.golang.org/2012/splash.article</a></p>
<p>动态语言将快速编译作为自身的一大亮点，像 C++ 那样的静态语言一般都有非常漫长的<br>编译和链接工作。而同样作为静态语言的 Go 语言，通过自身优良的构建机制，成功地<br>去除了这个弊端，使得程序的构建过程变得微不足道，拥有了像脚本语言和动态语言那样<br>的高效开发的能力。<br>Go 语言中另一个非常重要的特性就是它的构建速度（编译和链接到机器代码的速度），一般情况下构建一个程序的时间只需要数百毫秒到几秒。C 语言中“头文件”的概念却导致越来越多因为依赖关系而使得构建一个大型的项目需要长达几个小时的时间，而Go 语言采用包模型这个模型通过严格的依赖关系检查机制来加快程序构建的速度，提供了非常好的可量测性</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;more&lt;/p&gt;
    
    </summary>
    
      <category term="编程语言" scheme="http://www.aitozi.com/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/"/>
    
    
      <category term="golang" scheme="http://www.aitozi.com/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>Golang中的核心功能</title>
    <link href="http://www.aitozi.com/golang-specific-synax.html"/>
    <id>http://www.aitozi.com/golang-specific-synax.html</id>
    <published>2020-01-22T09:48:03.000Z</published>
    <updated>2020-01-22T12:40:03.982Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://juejin.im/post/5c9c64596fb9a070c40ade78" target="_blank" rel="noopener">https://juejin.im/post/5c9c64596fb9a070c40ade78</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://juejin.im/post/5c9c64596fb9a070c40ade78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://juejin.im/post/5c9c64596fb9a070c40ade78&lt;
    
    </summary>
    
      <category term="编程语言" scheme="http://www.aitozi.com/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/"/>
    
    
      <category term="golang" scheme="http://www.aitozi.com/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>Golang中的GC</title>
    <link href="http://www.aitozi.com/golang-gc.html"/>
    <id>http://www.aitozi.com/golang-gc.html</id>
    <published>2020-01-22T09:47:03.000Z</published>
    <updated>2020-01-22T12:31:01.245Z</updated>
    
    <content type="html"><![CDATA[<p>golang中的gc: <a href="https://github.com/qcrao/Go-Questions/blob/master/GC/GC.md" target="_blank" rel="noopener">https://github.com/qcrao/Go-Questions/blob/master/GC/GC.md</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;golang中的gc: &lt;a href=&quot;https://github.com/qcrao/Go-Questions/blob/master/GC/GC.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/qcrao/
    
    </summary>
    
      <category term="编程语言" scheme="http://www.aitozi.com/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/"/>
    
    
      <category term="golang" scheme="http://www.aitozi.com/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>常阅读博客</title>
    <link href="http://www.aitozi.com/blog-collection.html"/>
    <id>http://www.aitozi.com/blog-collection.html</id>
    <published>2020-01-22T01:58:30.000Z</published>
    <updated>2020-01-22T12:40:56.290Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p>
<p>优秀可以常阅读汲取的博客</p>
<a id="more"></a>
<h2 id="java学习"><a href="#java学习" class="headerlink" title="java学习"></a>java学习</h2><p>五月的仓颉: <a href="http://www.cnblogs.com/xrq730/p/5260294.html" target="_blank" rel="noopener">http://www.cnblogs.com/xrq730/p/5260294.html</a><br>hollischuang: <a href="http://www.hollischuang.com/" target="_blank" rel="noopener">http://www.hollischuang.com/</a><br>死磕java: <a href="http://cmsblogs.com/" target="_blank" rel="noopener">http://cmsblogs.com/</a></p>
<h2 id="jvm"><a href="#jvm" class="headerlink" title="jvm"></a>jvm</h2><p>阿里坤谷莫简豪 <a href="http://greenteajug.cn/" target="_blank" rel="noopener">http://greenteajug.cn/</a><br>你假笨: <a href="http://lovestblog.cn" target="_blank" rel="noopener">http://lovestblog.cn</a><br><a href="https://www.compressedrefs.com" target="_blank" rel="noopener">https://www.compressedrefs.com</a><br>RednaxelaFX: <a href="http://rednaxelafx.iteye.com/" target="_blank" rel="noopener">http://rednaxelafx.iteye.com/</a></p>
<h2 id="大数据"><a href="#大数据" class="headerlink" title="大数据"></a>大数据</h2><p>阿里流计算工程师: <a href="http://www.cnblogs.com/fxjwind/" target="_blank" rel="noopener">http://www.cnblogs.com/fxjwind/</a><br>wuchong: <a href="http://wuchong.me/" target="_blank" rel="noopener">http://wuchong.me/</a><br>yuzhao: <a href="http://chenyuzhao.me/" target="_blank" rel="noopener">http://chenyuzhao.me/</a><br>vinoyang: <a href="http://blog.csdn.net/yanghua_kobe?viewmode=contents" target="_blank" rel="noopener">http://blog.csdn.net/yanghua_kobe?viewmode=contents</a><br>Imalds: <a href="http://blog.csdn.net/lmalds?viewmode=contents" target="_blank" rel="noopener">http://blog.csdn.net/lmalds?viewmode=contents</a><br>林意群 <a href="http://blog.csdn.net/androidlushangderen" target="_blank" rel="noopener">http://blog.csdn.net/androidlushangderen</a><br>宇宙湾: <a href="https://yuzhouwan.com" target="_blank" rel="noopener">https://yuzhouwan.com</a><br><a href="http://www.jasongj.com/" target="_blank" rel="noopener">http://www.jasongj.com/</a><br>张茄子: <a href="https://io-meter.com" target="_blank" rel="noopener">https://io-meter.com</a><br>matt: <a href="http://matt33.com" target="_blank" rel="noopener">http://matt33.com</a><br>coding-geek: <a href="http://coding-geek.com/" target="_blank" rel="noopener">http://coding-geek.com/</a><br>duruofei: <a href="http://armsword.com/" target="_blank" rel="noopener">http://armsword.com/</a><br><a href="http://cmsblogs.com/" target="_blank" rel="noopener">http://cmsblogs.com/</a></p>
<h2 id="大神"><a href="#大神" class="headerlink" title="大神"></a>大神</h2><p>褚霸 <a href="http://blog.yufeng.info/" target="_blank" rel="noopener">http://blog.yufeng.info/</a></p>
<h2 id="Go"><a href="#Go" class="headerlink" title="Go"></a>Go</h2><p><a href="http://colobu.com/" target="_blank" rel="noopener">http://colobu.com/</a></p>
<p><a href="https://changkun.de/" target="_blank" rel="noopener">https://changkun.de/</a></p>
<p><a href="https://sanyuesha.com" target="_blank" rel="noopener">https://sanyuesha.com</a></p>
<h2 id="scala学习"><a href="#scala学习" class="headerlink" title="scala学习"></a>scala学习</h2><p><a href="http://hongjiang.info/scala/" target="_blank" rel="noopener">http://hongjiang.info/scala/</a></p>
<h2 id="面试"><a href="#面试" class="headerlink" title="面试"></a>面试</h2>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;[toc]&lt;/p&gt;
&lt;p&gt;优秀可以常阅读汲取的博客&lt;/p&gt;
    
    </summary>
    
      <category term="杂七杂八" scheme="http://www.aitozi.com/categories/%E6%9D%82%E4%B8%83%E6%9D%82%E5%85%AB/"/>
    
    
  </entry>
  
  <entry>
    <title>Golang中的函数与方法</title>
    <link href="http://www.aitozi.com/go-function-in-golang.html"/>
    <id>http://www.aitozi.com/go-function-in-golang.html</id>
    <published>2020-01-21T02:42:03.000Z</published>
    <updated>2020-01-22T12:39:47.715Z</updated>
    
    <content type="html"><![CDATA[<p>golang中的函数与方法</p>
<a id="more"></a>
<h1 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h1><h3 id="函数返回多值"><a href="#函数返回多值" class="headerlink" title="函数返回多值"></a>函数返回多值</h3><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">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">swap</span><span class="params">(x, y <span class="keyword">string</span>)</span> <span class="params">(<span class="keyword">string</span>, <span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line">   <span class="keyword">return</span> y, x</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">   a, b := swap(<span class="string">"Google"</span>, <span class="string">"Runoob"</span>)</span><br><span class="line">   fmt.Println(a, b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="函数参数可以是值传递也可以是引用传递"><a href="#函数参数可以是值传递也可以是引用传递" class="headerlink" title="函数参数可以是值传递也可以是引用传递"></a>函数参数可以是值传递也可以是引用传递</h3><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></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">swap</span><span class="params">(x, y <span class="keyword">int</span>)</span> <span class="title">int</span></span> &#123;</span><br><span class="line">   <span class="keyword">var</span> temp <span class="keyword">int</span></span><br><span class="line"></span><br><span class="line">   temp = x <span class="comment">/* 保存 x 的值 */</span></span><br><span class="line">   x = y    <span class="comment">/* 将 y 值赋给 x */</span></span><br><span class="line">   y = temp <span class="comment">/* 将 temp 值赋给 y*/</span></span><br><span class="line">  </span><br><span class="line">   <span class="keyword">return</span> temp;</span><br><span class="line">&#125;</span><br><span class="line">swap(a, b)</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></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">swap</span><span class="params">(x *<span class="keyword">int</span>, y *<span class="keyword">int</span>)</span></span> &#123;</span><br><span class="line">   <span class="keyword">var</span> temp <span class="keyword">int</span></span><br><span class="line">   temp = *x    <span class="comment">/* 保持 x 地址上的值 */</span></span><br><span class="line">   *x = *y      <span class="comment">/* 将 y 值赋给 x */</span></span><br><span class="line">   *y = temp    <span class="comment">/* 将 temp 值赋给 y */</span></span><br><span class="line">&#125;</span><br><span class="line">swap(&amp;a, &amp;b) 传入的是a和b的值地址</span><br></pre></td></tr></table></figure>
<p>这两个取决于函数定义时的接受参数的类型是不是引用类型</p>
<h3 id="函数作为另一个函数的实参"><a href="#函数作为另一个函数的实参" class="headerlink" title="函数作为另一个函数的实参"></a>函数作为另一个函数的实参</h3><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">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">"fmt"</span></span><br><span class="line">   <span class="string">"math"</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="comment">/* 声明函数变量 */</span></span><br><span class="line">   getSquareRoot := <span class="function"><span class="keyword">func</span><span class="params">(x <span class="keyword">float64</span>)</span> <span class="title">float64</span></span> &#123;</span><br><span class="line">      <span class="keyword">return</span> math.Sqrt(x)</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">   fmt.Println(getSquareRoot(<span class="number">9</span>))</span><br><span class="line"></span><br><span class="line">&#125;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</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="comment">// 声明一个函数类型</span></span><br><span class="line"><span class="keyword">type</span> cb <span class="function"><span class="keyword">func</span><span class="params">(<span class="keyword">int</span>)</span> <span class="title">int</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="title">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    testCallBack(<span class="number">1</span>, callBack) <span class="comment">// 这个是下面声明的callback函数</span></span><br><span class="line">    testCallBack(<span class="number">2</span>, <span class="function"><span class="keyword">func</span><span class="params">(x <span class="keyword">int</span>)</span> <span class="title">int</span></span> &#123; <span class="comment">// 类似java中的匿名函数</span></span><br><span class="line">        fmt.Printf(<span class="string">"我是回调，x：%d\n"</span>, x)</span><br><span class="line">        <span class="keyword">return</span> x</span><br><span class="line">    &#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">testCallBack</span><span class="params">(x <span class="keyword">int</span>, f cb)</span></span> &#123;</span><br><span class="line">    f(x)</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">callBack</span><span class="params">(x <span class="keyword">int</span>)</span> <span class="title">int</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">"我是回调，x：%d\n"</span>, x)</span><br><span class="line">    <span class="keyword">return</span> x</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h3><p>Go 语言支持匿名函数，可作为闭包。匿名函数是一个”内联”语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量，不必申明。</p>
<p>以下实例中，我们创建了函数 getSequence() ，返回另外一个函数。该函数的目的是在闭包中递增 i 变量，代码如下：</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="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">getSequence</span><span class="params">()</span> <span class="title">func</span><span class="params">()</span> <span class="title">int</span></span> &#123;</span><br><span class="line">   i:=<span class="number">0</span></span><br><span class="line">   <span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">()</span> <span class="title">int</span></span> &#123;</span><br><span class="line">      i+=<span class="number">1</span></span><br><span class="line">     <span class="keyword">return</span> i  </span><br><span class="line">   &#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">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">   <span class="comment">/* nextNumber 为一个函数，函数 i 为 0 */</span></span><br><span class="line">   nextNumber := getSequence()  </span><br><span class="line"></span><br><span class="line">   <span class="comment">/* 调用 nextNumber 函数，i 变量自增 1 并返回 */</span></span><br><span class="line">   fmt.Println(nextNumber())</span><br><span class="line">   fmt.Println(nextNumber())</span><br><span class="line">   fmt.Println(nextNumber())</span><br><span class="line">   </span><br><span class="line">   <span class="comment">/* 创建新的函数 nextNumber1，并查看结果 */</span></span><br><span class="line">   nextNumber1 := getSequence()  </span><br><span class="line">   fmt.Println(nextNumber1())</span><br><span class="line">   fmt.Println(nextNumber1())</span><br><span class="line">&#125;</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><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">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">1</span><br><span class="line">2</span><br></pre></td></tr></table></figure>
<h3 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h3><p>在Go 语言中同时有函数和方法，函数是指不属于任何结构体、类型的方法，也就是说，函数是没有接收这的；而方法是有接收者的，我们说的方法要么是属于一个结构体，要么是属于一个新定义的类型。而ava是面向对象的语言，其实只有方法，没有函数这一说，要类比的话，go中的函数可能类似于java中的静态方法，而go中的方法则类似于java中的成员方法。一个方法就是一个包含了接受者的函数，接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(variable_name variable_data_type)</span> <span class="title">function_name</span><span class="params">()</span> [<span class="title">return_type</span>]</span>&#123;</span><br><span class="line">   <span class="comment">/* 函数体*/</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>方法的声明和函数类似，他们的区别是：方法在定义的时候，会在<code>func</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></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">"fmt"</span>  </span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 定义结构体 */</span></span><br><span class="line"><span class="keyword">type</span> Circle <span class="keyword">struct</span> &#123;</span><br><span class="line">  radius <span class="keyword">float64</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">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  <span class="keyword">var</span> c1 Circle</span><br><span class="line">  c1.radius = <span class="number">10.00</span></span><br><span class="line">  fmt.Println(<span class="string">"圆的面积 = "</span>, c1.getArea())</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//该 method 属于 Circle 类型对象中的方法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c Circle)</span> <span class="title">getArea</span><span class="params">()</span> <span class="title">float64</span></span> &#123;</span><br><span class="line">  <span class="comment">//c.radius 即为 Circle 类型对象中的属性</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">3.14</span> * c.radius * c.radius</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>Go 没有面向对象，而我们知道常见的 Java。</p>
<p>C++ 等语言中，实现类的方法做法都是编译器隐式的给函数加一个 this 指针，而在 Go 里，这个 this 指针需要明确的申明出来，其实和其它 OO 语言并没有很大的区别。</p>
<p>在 C++ 中是这样的:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Circle &#123;</span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">float</span> <span class="title">getArea</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> <span class="number">3.14</span> * radius * radius;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">float</span> radius;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 其中 getArea 经过编译器处理大致变为</span></span><br><span class="line"><span class="function"><span class="keyword">float</span> <span class="title">getArea</span><span class="params">(Circle *<span class="keyword">const</span> c)</span> </span>&#123;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在 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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c Circle)</span> <span class="title">getArea</span><span class="params">()</span> <span class="title">float64</span></span> &#123;</span><br><span class="line">  <span class="comment">//c.radius 即为 Circle 类型对象中的属性</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">3.14</span> * c.radius * c.radius</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;golang中的函数与方法&lt;/p&gt;
    
    </summary>
    
      <category term="编程语言" scheme="http://www.aitozi.com/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/"/>
    
    
      <category term="golang" scheme="http://www.aitozi.com/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>人生的意义是什么？活着的意义是什么？</title>
    <link href="http://www.aitozi.com/whats-the-point-of-our-live.html"/>
    <id>http://www.aitozi.com/whats-the-point-of-our-live.html</id>
    <published>2019-09-15T06:32:20.000Z</published>
    <updated>2019-09-15T06:46:47.924Z</updated>
    
    <content type="html"><![CDATA[<blockquote>
<p>声明：本文章是摘录自知乎的回答，触发我去做摘录的原因是在和兔子辩论人生的意义的时候，想去翻阅之前在知乎看到的一篇高赞回答，竟然发现答案已经被折叠了。为了避免这些曾经让我陷入思考的文章流失，也为了能在我的自留地留下这些思考的痕迹。</p>
</blockquote>
<p>“不论将来做什么，或是伟大或是平凡，所有人都将面临死亡，每个人都会被时间所遗忘，消失在宇宙中。人的一生重复地上学放学或是上班下班，一切都这样无聊。从小就被说要做对社会有贡献的人，或是名人，牛人，有钱人，争权夺势，或是苦心研究，可是这些到最后又有什么意义呢？反正人都得死。什么也没有。有人说，人死了，可他的贡献在，会被后人永远铭记，也可以帮助国家和人类历史的发展，可是国家又是什么？就算做出了改变国家或者人类历史的事情，可是地球也终将毁灭呀？到时候人类和国家也都消失了，所有的一切都不存在了。为什么还要活着呢？或者是为什么还要努力地、拼命地活着呢？不论是好的还是坏的都将消失，为什么要做别人眼中所谓好的。”</p>
<a id="more"></a>
<p>以上是知乎上的一个问题，以下是原答案，发布于2014-08-09<br>作者：光的记忆<br>链接：<a href="https://www.zhihu.com/question/24329745/answer/28988224" target="_blank" rel="noopener">https://www.zhihu.com/question/24329745/answer/28988224</a></p>
<hr>
<p>我今天可以一次性地解决题主的这个问题。</p>
<p>只是解决的方式恐怕和题主期待的不一样。我们期待的解决方式是有个人来告诉我们人生有什么意义。而我提供的答案是：我可以告诉你怎么摆脱这个问题的困扰。摆脱这个问题的困扰其实不需要回答这个问题。</p>
<p>让我们顺着思路一步一步来。</p>
<p>这个世俗社会以金钱来衡量人的成功，我们不想和大多数人一样碌碌无为地过完一生，那样我们的人生就没有意义了。我们想要活得不一样，想要脱颖而出成为人上人，但是这世界上那么多人，成功的人那么少，这个概率小得让我们感到绝望。<br>于是，一方面，我们想要成功，想要被世俗认可，另一方面，我们觉得自己成功的机会渺茫，就算退而求其次找个深爱的人过一生也不是那么容易，我们不禁开始琢磨：如果一定要和所有碌碌无为的人一样浑浑噩噩地活着，我的人生还有什么意义？</p>
<p>关于人生的意义，有这三种可能性。</p>
<p>1.人生有意义，意义就是赚大钱，当大官，和相爱的人在一起。</p>
<p>2.人生有意义，意义是某个1里面没提到的东西。</p>
<p>3.人生无意义。</p>
<p>已经没有其他可能性了。那么这三个里面必然有一个是正确的。那么什么样的东西才能叫做意义？意义就是能够让你觉得我活了这一辈子，因为有了“它”，所以没白活。有了它之后，我的人生达到了真正的满足，即使死去也毫无遗憾，它是我毕生追求的东西，是我一切行为的终极方向。听上去有点像“信仰”。但是意义和信仰不同，信仰是一种你必须去追求的才能达到的东西，<strong>而意义是本来就存在的东西。是本来就有的状态</strong>。</p>
<p>假如1是真的。</p>
<p>那么假如有一天你真的赚到了大钱，成为了一名企业家，比如说刘强东那样，有钱，有奶茶，出任ceo，迎娶白富美，走向人生巅峰。那么你的人生就是有意义的了。那么你这一辈子就没白活，那么你死而无憾。那我让你马上就死，你会死吗？</p>
<p>你会说：</p>
<p>你这不是坑爹吗！好不容易有钱了，好不容易有奶茶了，你至少先让我爽一阵子吧。你要是现在就让我死了，那我赚这些钱还有什么意义？所以说你想要的并不仅仅是钱而已，你想要的是有钱之后的那种爽。那你想爽多久？我觉得多久你都不会嫌久。。。<br>基本上所有成功学类的书都可以归为这第一类，成功学一般来说一整本书主要讲两件事：1.坚持梦想。2.努力。成功学的书会不断暗示你最后的成功是你人生唯一的目的和意义。但是它只敢暗示，不敢挑明了写出来。因为成功的概率太低了，低于1%。而且成功学的书会暗示你，假如你没成功，怪你自己不够努力，或者没有坚持住自己的梦想。于是就把责任推得一干二净。然而正常人的常识告诉我们努力和成功（至少是成功学里描述的成功）并没有必然联系，成功的希望太渺茫了。<br>那一个一辈子都没希望成功的人活着又有什么意义？没有意义活着又和死了有什么区别？<br>由于这个希望太渺茫，与其活在害怕人生没有意义的恐惧之中，不如干脆就不把这个当成意义，我们可以找一些更容易达到的目标当做意义，来获得内心的平静。这就引出了选项2。</p>
<p>假如2是真的。</p>
<p>人生有意义，意义是某个其他的东西。是什么东西呢？基本上所有鸡汤心理学书籍都可以归为这一类。这些书里面对人生的意义大致可以分为两大类：</p>
<p>1.爱，尤其是给予爱。</p>
<p>2.旅途沿路的风景。</p>
<p>比如我们从另外一个热门问题：人终会消失，人生有何意义？为什么要活着？里面最高票@GayScript的答案，列举了很多人生中美好的东西。我整理如下：</p>
<p>1.好吃的，比如鸭脖、扯面、麻辣香锅。</p>
<p>2.好听的音乐，比如youngandbeautiful等</p>
<p>3.好看的小说，比如《白夜行》等</p>
<p>4.好看的电视，比如《甄嬛传》</p>
<p>第二高票@陈粒的答案，整理如下：</p>
<p>1.明白些道理——理性思考</p>
<p>2.遇见有趣的事——乐观心态</p>
<p>第三高票@唐棣的答案，整理如下：</p>
<p>1.像吃蛋糕和甜甜圈一样，在于吃的过程。</p>
<p>Gayscript的答案，好吃的好听的好看的，都是美好体验的一部分，也就是人生旅途的一部分。而陈粒的答案，思考和心态，也是人生的一个过程。唐棣的答案，就更加明显了，用吃的过程比喻人生的过程，强调享受这个过程。这是一件很矛盾的事情。一方面整个社会都在吹捧有钱有地位还有真爱（主要还是前两项）的人，而另一方面鸡汤心理学的书籍都非要把读者的视线从金钱地位这种功利的东西上面拉回来。为什么要拉回来呢？就是因为这些其他的小事情更容易做到啊，也不会让你陷入害怕努力一辈子也没成功而找不到活着的意义的恐惧中。</p>
<p>那题主看到这里想必会嘀咕：就因为选项1太难达到，所以就告诉自己“那我就不要选项1了，我从简单的东西里面找意义”这不是自欺欺人吗！</p>
<p>别忘了，选项1真正吸引你的，不是金钱和地位，而是那种爽的感觉。</p>
<p>题主可能又会说：是啊，但那种爽只有金钱和地位还有深爱的人才能带来啊，你吃点东西看点电视怎么可能和有钱的爽相提并论！</p>
<p>其实。。。还有更爽的。。。你信吗？而且这种爽都不用你努力赚钱成功。</p>
<p>以大脑分泌的多巴胺的含量多少来量化爽的话，（多巴胺：传递快乐、兴奋情绪的功能，又被称作快乐物质。）</p>
<p>美食，可以提升多巴胺到150%。这就是世界上那么多吃货的原因。</p>
<p>性，可以提升多巴胺到200%。日常生活中你基本找不到比性高潮更爽的时刻了。</p>
<p>天天吃喝玩乐，天天打炮的生活，你有兴趣吗？你既然到知乎来提问题了，你多半会觉得，那种生活毫无目标，没有过的价值。</p>
<p>是因为不够爽吗？我们还有更爽的东西。</p>
<p>可卡因，可以提升350%的多巴胺。</p>
<p>冰毒，可以提升1200%。</p>
<p>把你记忆中最爽的那次性高潮，快感乘以10倍，都达不到冰毒带给你的体验。你会觉得你在世界之颠，你是世界上最快乐的人，你精力无限，你能力无限，你可以飞起来。在药物面前，一切生活中的爽都变得苍白无力。那假如我给你提供足量的冰毒，让你不间断地吸，一直爽到死，你愿意吗？</p>
<p>你愿意这样活吗？恐怕你想到那种生活脑子里就只有恐惧了吧。</p>
<p>因为你想要的，不仅仅是爽而已。你会觉得只有爽的人生过于低俗了，你想要一个高大上的意义。</p>
<p>如果我跟你说，你要为了你爱的人和爱你的人而活着。你可能会问：凭什么？我多累啊？<br>如果我跟你说，你要为了造福社会，为社会作贡献，为人类作贡献而活着，比如比尔盖茨做慈善，比如甘地和曼德拉为了和平，比如奥巴马为了全美国人的幸福。你可能会说：那都太远了，我自己还没活明白呢，哪有心情管别人。</p>
<p>你说：有没有不那么“道貌岸然”，又很高大上的理由？我要一个真正神圣而终极的意义，不要和这些俗事俗人扯在一起。</p>
<p>这个问题我思考过超过10年，后来我发现，真正超俗的终极意义，所有想脱离平凡而向往最终的伟大的进化，最后都指向了同一个归宿——宗教。<br>只有神，才能给你一个终极的意义。</p>
<p>你说：。。。。。。我是无神论，你给我来点实际的。</p>
<p>好。</p>
<p>无神论者，最后的终极意义，也就是进化的终极意义，是推动人类向往更高级的文明，更高级的秩序发展，而我们一生也许只是帮助推动这个历史进程往前走短短一步而已。（理论支持：《非零年代》）</p>
<p>你说：。。。。。。这是人类的意义，我根本活不到那天，太虚了，有没有接地气一点的，你就不能把注意力放在我身上，别总扯全人类什么的。</p>
<p>我说：。。。。。。我弄死你。你现在为什么不马上去死？阻止你死的原因就是你活着的意义。</p>
<p>你说：我就是不想死啊，没什么特别原因。这不能算是意义吧？而且我感觉我还没活明白，没找到意义，我死也死得不明不白啊。这也不算是意义吧？</p>
<p>对方辩友你赢了。。。我已经没有更好的回答了。</p>
<p>到目前为止出现过的所有备选答案：</p>
<p>1.好吃的，好听的，好看的<br>2.明白些道理，遇见有趣的事<br>3.像吃蛋糕和甜甜圈一样，在于吃的过程。<br>4.纯粹为了快感而嗑药。<br>5.为了我爱的人和爱我的人。<br>6.为社会和他人作贡献。<br>7.为神服务。<br>8.推动人类进化。</p>
<p>每一个都可以作为活着的意义，但是如果你真的要问为什么，我活着就为了吃吗？我活着就为了那点快感吗？我为什么要学习新事物有什么意义？我为什么要为了别人活着？我为什么要奉献自己给全人类？我就不能自己好好活着吗？<br>如果你愿意，你可以为其中任何一项而活着；如果你不想，哪一项也没有足够的说服力让你非为这个活不可。</p>
<p>那最终答案是什么？如果你已经问到这里了，就只剩最后一个可能性了。<strong>人活着没有任何意义。</strong>。你必须搞清楚这个先后顺序，你是先存在于这个世界上，然后才开始寻找意义的。<strong>你不是因为有意义才活着。</strong> <strong>你是因为活着才去寻找意义。</strong><br><strong>你跟所有物体，石头，树木，动物一样，都只是存在。他们的存在没有意义，你的存在也没有意义。</strong>但是作为人类，你有思维，你有自我察觉，你有价值判断，所以你觉得自己应该有一个意义。（理论支持：斯蒂芬·霍金《TheGrandDesign》）</p>
<p>而且，你发现周围的人得过且过，而自己需要一个真正的意义，你觉得自己很特别。实际上你确实很特别，但不是你想的那种特别。你的特别之处仅仅在于——可能你目前的多巴胺水平比他们低而已。多巴胺，大脑中的激素，决定了你怎么思考。当多巴胺偏低的时候，人就开始思考生命的意义，生活的意义。试图用一种理智的东西来说服自己忍受不快乐的生活。如果多巴胺水平更低，就会陷入抑郁情绪。如果你接触抑郁症患者，你会发现他们经常说的一句就是：那又有什么意义？活着又有什么意义？如果你多巴胺再低一点，变成重症抑郁，那时你就开始想要自杀了，因为你觉得活着没意义。<br>而如果你的多巴胺和大多数人水平一样，你基本就会和大多数人一样，觉得就这样活着挺好啊。生活中这么多美好的事情，就像gayscript列举的那样，有好吃的好玩的好看的，这样的生活不是挺好吗？如果你再高一点，你就会觉得生活简直太美好了，根本不需要什么意义，活着本身就已经是最大的幸运。阳光照着你你很开心，走在草地上你很开心，恋人的一个笑，那就是你生命的全部意义。<br>但是我们不要急，多巴胺的水平不是固定的，是可以随时调整的。往往我们成长的每个阶段会有不同的想法，有时两个想法截然相反。比如小学时候你很积极学习，初中时候你很厌学这类的，有可能会是多巴胺的原因。（也有可能是别的原因）<br>为什么所有鸡汤心理学都告诉你在怀疑人生的时候，要体验旅途，要爱别人？因为这么做了之后，你的多巴胺水平就会升高啊！然后你就不会纠结到底有什么意义了！<br>如果你说：那不是自欺欺人吗？只是把问题拖到后面了而已。</p>
<p><strong>不，事实是，问题根本就不存在！</strong></p>
<p>你之前为什么会觉得钱和地位是意义？只不过是你对钱和地位有需求而已。</p>
<p>我又要拿出万能的马斯洛了。。。</p>
<p>你需要钱和地位和爱情，来满足你（从下往上）第二层到第四层的需求。只是因为你有这种需求，你就会以为这就是你的意义。也就是说，意义只不过是心理需求的一种体现而已。事实上当我几年前发现真正神圣而终极的意义只有宗教的时候，我开始明白<strong>意义不是一个固定的东西，它是一个可变的东西，在不同的人生阶段代表不同的东西</strong>。后来的学习研究也证实了我的观点，它真的是随着需求而变的。<br><strong>正确的顺序是：先存在=&gt;因为活着而有需求=&gt;把需求当做意义。</strong></p>
<p>首先你要接受一个事实，而不是逃避这个事实，那就是你活着。只有当你真正接受了这个事实，才会开始想：既然现在我活着，我可以做点什么？</p>
<p><strong>为什么一定要接受这个事实？就像心理学上的接纳不完美的自己一样，接纳自己不完美的生活，接纳自己不需要意义的存在。你不是因为什么意义才出生的，你就这么突然地活着了，你坚持活下去的原因就是你不想死而已。无论你这一生的意义是什么，你活着的这一生所做的选择都是根据短期长期快感决定的，而且在不久的将来，你也会突然地死去。整个人类的进化也许有意义，但是这个视角太大了基本上和你没什么关系。你就这么活着，就这么存在着，也将会就这么死去。你没有别的选择，这是事实。你就是你，你活着就是活着。</strong></p>
<p>举个最简单的例子，你不会因为2+2=4而苦恼，你不会想2+2为什么等于4。因为为2+2=4而苦恼是没有意义的，因为你接受了2+2=4。（理论来源：耶鲁公开课《死亡》）</p>
<p>没有意义会让你变空虚了吗？</p>
<p><strong>不，一旦接受了这个设定，你就从意义中解放了出来。没有了意义的束缚，你应该更自由，因为你可以做自己想做的事情而不是意义让你做的事情，你可以真正掌控自己的生活，让它按照你设想的方式发展。</strong><br>所以不是先找到意义然后再活着，而是先努力生活，然后你自然会给自己赋予一个自己满意的意义。<br>认真赚钱，认真去爱，认真体验生命的美好，认真学习，认真见识新世界。你想让你的人生在哪方面拓展就在哪方面加油。这就是热爱生活。然后你就会明白为什么活着。这就是真正完全摆脱这种问题的困扰的方法，那就是积极，乐观，阳光地去拥抱生活！</p>
]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;声明：本文章是摘录自知乎的回答，触发我去做摘录的原因是在和兔子辩论人生的意义的时候，想去翻阅之前在知乎看到的一篇高赞回答，竟然发现答案已经被折叠了。为了避免这些曾经让我陷入思考的文章流失，也为了能在我的自留地留下这些思考的痕迹。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“不论将来做什么，或是伟大或是平凡，所有人都将面临死亡，每个人都会被时间所遗忘，消失在宇宙中。人的一生重复地上学放学或是上班下班，一切都这样无聊。从小就被说要做对社会有贡献的人，或是名人，牛人，有钱人，争权夺势，或是苦心研究，可是这些到最后又有什么意义呢？反正人都得死。什么也没有。有人说，人死了，可他的贡献在，会被后人永远铭记，也可以帮助国家和人类历史的发展，可是国家又是什么？就算做出了改变国家或者人类历史的事情，可是地球也终将毁灭呀？到时候人类和国家也都消失了，所有的一切都不存在了。为什么还要活着呢？或者是为什么还要努力地、拼命地活着呢？不论是好的还是坏的都将消失，为什么要做别人眼中所谓好的。”&lt;/p&gt;
    
    </summary>
    
      <category term="深度好文" scheme="http://www.aitozi.com/categories/%E6%B7%B1%E5%BA%A6%E5%A5%BD%E6%96%87/"/>
    
    
      <category term="哲思" scheme="http://www.aitozi.com/tags/%E5%93%B2%E6%80%9D/"/>
    
  </entry>
  
  <entry>
    <title>A Deep-Dive into Flink&#39;s Network Stack</title>
    <link href="http://www.aitozi.com/A-Deep-Dive-into-Flink&#39;s-Network-Stack.html"/>
    <id>http://www.aitozi.com/A-Deep-Dive-into-Flink&#39;s-Network-Stack.html</id>
    <published>2019-06-15T20:47:03.000Z</published>
    <updated>2019-06-16T16:39:52.033Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p>
<p>本文翻译自flink官网的一篇博文，详细介绍Flink架构中的网络栈，下面就让我们来一睹为快吧。</p>
<a id="more"></a>
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>Flink网络栈是flink中的核心组件，是flink-runtime模块的一部分。它连接了所有TaskManager中独立的工作单元（subtasks）。这是流入数据流经的地方，因此你所观察到的吞吐量和延迟都和他息息相关，可以说Flink的网络栈决定了Flink框架本身性能的好坏。和TaskManager，Jobmanager之间通信所使用的akka rpc框架不同的是，flink网络栈采用了更底层的网络api，使用的是Netty框架。</p>
<h3 id="Logical-View"><a href="#Logical-View" class="headerlink" title="Logical View"></a>Logical View</h3><p><img src="https://flink.apache.org/img/blog/2019-06-05-network-stack/flink-network-stack1.png" alt="logical view"></p>
<p>它抽象了以下三个概念的不同设置：</p>
<ul>
<li>Subtask output type (ResultPartitionType):<ul>
<li>pipelined (bounded or unbounded)： 上游一产生数据，就一条一条的往下游发送，作为有界或无界的数据流</li>
<li>blocking: 只有当上游的全部结果就绪之后才向下游发送数据</li>
</ul>
</li>
<li>Scheduling type:<ul>
<li>all at once (eager)： 同时部署所有的subtask（流式应用采用这种模式）</li>
<li>next stage on first output (lazy): 当上游的生产者开始有输出结果的时候，才开始不是下游的subtask，是一种lazy模式</li>
<li>next stage on complete output: 当上游的数据全部就绪之后才开始下游subtask的部署。</li>
</ul>
</li>
<li>Transport:<ul>
<li>high throughput: 不采用一条一条发送数据的模式，Flink缓存一批数据到network buffer中，攒批发送。这个减少了网络开销的单条边际成本，带来了高吞吐</li>
<li>low latency via buffer timeout: 通过减少发送未攒满一个buffer的timeout时间，牺牲一定的吞吐带来更低的延迟        </li>
</ul>
</li>
</ul>
<p>我们将在下面这部分讨论吞吐和延迟的优化。这一部分将查看网络栈的物理层，对于这部分，我们将详细阐述output type以及调度模式。首先，subtask的输出类型和调度类型是紧密交织在一起的，两者的特定组合才有效。Pipelined result partition是流式的输出，流式输出需要将数据发送到一个正在工作的subtask，因此目标task就需要在上游结果产出下发之前deploy完成或者在任务启动最初完成deploy。 Batch作业产出有限的结果，而stream作业产出无限的结果。</p>
<p>Batch作业也可以以阻塞的方式产出结果，具体取决于operator和conector的使用模式。在这种方式下，下游算子需要再上游结果完全ready之后才进行部署，在这种方式下资源使用效率会更高.</p>
<p>下表总结了有效的组合方式：</p>
<table>
<thead>
<tr>
<th>Output Type</th>
<th>Scheduling Type</th>
<th>Applies to…</th>
</tr>
</thead>
<tbody>
<tr>
<td>pipelined, unbounded</td>
<td>all at once</td>
<td>Streaming jobs</td>
</tr>
<tr>
<td></td>
<td>next stage on first output</td>
<td>n/a¹</td>
</tr>
<tr>
<td>pipelined, bounded</td>
<td>all at once</td>
<td>n/a²</td>
</tr>
<tr>
<td></td>
<td>next stage on first output</td>
<td>Batch jobs</td>
</tr>
<tr>
<td>blocking</td>
<td>next stage on complete output</td>
<td>Batch jobs</td>
</tr>
</tbody>
</table>
<p>【1】: 当前没有再flink中使用<br>【2】: 在<a href="https://flink.apache.org/roadmap.html#batch-and-streaming-unification" target="_blank" rel="noopener">Batch/Streaming unification</a>完成后可能在流式作业中使用</p>
<p>另外对于有多个输入的subtask的batch作业，调度开始有两种模式，在所有input产出数据或者任意一个输入产出数据的时候。</p>
<h3 id="Physical-Transport"><a href="#Physical-Transport" class="headerlink" title="Physical Transport"></a>Physical Transport</h3><p>为了理解真实的吴礼数据的连接，请回想一下，在Flink中，不同的task可能会共享一个同一个slot，通过slot sharing group机制，TaskManager也可以提供多个slot来允许一个task的多个subtask跑在一个TaskManager上。</p>
<p>举个下图中的例子，我们假想一个有四个并发的任务，部署在两个分别有2个slot的TaskManager上。TaskManager 1 运行subtask A.1，A.2，B.1 和 B.2 而TaskManager 2 运行subtaskA.3,A.4,B.3,B.4。假设A和B之间的shuffle方式是<code>keyBy()</code>,这样在每一个TaskManager上都有2x4个逻辑连接，有些走local的，有些是通过网络的，如下图所示。</p>
<p><img src="https://github.com/Aitozi/images/blob/master/flink/flink-network-stack-1.jpg?raw=true" alt></p>
<p>不同task之间的每个（远程）网络连接，都将在flink网络栈中获得自己的TCP通道，但是，如果同一个任务的不同子任务被调度到同一个TaskManager上，他们和另一个TaskManager上的TCP连接将会共享（多路复用），在我们的例子中A.1 -&gt; B.3, A.1 -&gt; B.4 以及A.2 -&gt; B.3,A.2 -&gt; B.4将会复用一个tcp连接（这里有点疑问，按我的理解应该是每个TM之间都会共享channel才对）。</p>
<p><img src="https://flink.apache.org/img/blog/2019-06-05-network-stack/flink-network-stack2.png" alt></p>
<p>每个subtask的输出被称作<code>ResultPartition</code>, 每一个又被细分为<code>ResultSubPartition</code>,一个逻辑channel会有一个。在这个阶段，Flink已经不再单独处理每条记录了，而是将一组序列化完的数据打包拷贝到network buffer中，每一个subtask中local buffer pool（发送端和接收端各有一个pool）中所能获取的最多的buffer数量是通过以下的配置决定的</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">channels * buffers-per-channel + floating-buffers-per-gate</span><br></pre></td></tr></table></figure>
<p>通常一个TaskManager上总的buffer数量不需要配置，在需要是可以查看相关的配置项<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.8/ops/config.html#configuring-the-network-buffers" target="_blank" rel="noopener">Configuring the Network Buffers</a></p>
<h3 id="Inflicting-Backpressure-1"><a href="#Inflicting-Backpressure-1" class="headerlink" title="Inflicting Backpressure (1)"></a>Inflicting Backpressure (1)</h3><p>当一个subtask的发送端的buffer用尽之后 - buffer可能用于result subpartition的buffer队列，也可能正用于低阶的Netty的网络栈中尚未回收。在这种情况下producer就被block住，无法进一步发送数据。</p>
<p>消费端也是一样的方式，从底层netty网络栈传输来的buffer需要通过network buffer才能被正确消费，如果在消费端的network buffer用尽了，Flink将停止从channel中读入数据，直到有新的network buffer用来做数据的转化。这种情况下将会反压上游所有通过这个tcp channel的多路复用发送数据的生产端，这样也就限制了其他下游的消费能力。在下图阐述了一个有性能瓶颈的B.4 subtask将会引起backpressure，最终将会引起B.3无法消费和处理新的数据，尽管B.3还有足够的network buffer。</p>
<p><img src="https://flink.apache.org/img/blog/2019-06-05-network-stack/flink-network-stack3.png" alt></p>
<p>为了彻底解决这个问题，Flink1.5引入了流控机制。</p>
<h3 id="Credit-based-Flow-Control"><a href="#Credit-based-Flow-Control" class="headerlink" title="Credit-based Flow Control"></a>Credit-based Flow Control</h3><p>基于流控的网络传输能够确保正在传输的数据在接受端都有可以接受的buffer（传输的数据都是得到确认之后才可以向下游发送的）。新的传输模式仍然基于Flink原有的network buffer的能力，在其上做了一些扩展。除了仅仅有一个共享的本地buffer pool，每一个remote inputchannel现在会有其自己独占的一批buffer池，相对地，共享池中的buffer就被称为发floating buffer因为他们对于每一个inputchannel都是可以获取的。</p>
<p>接收端现在将会以<code>Credits</code>的形式通知发送端告知它能够接收多少buffer（1 buffer = 1 credit）。 每一个result subpartition将持续记录相对应channel的credits。只有当下游的通道有credits的时候，才会通过netty将上游的buffer发送出去，每发送一个buffer，就会减去一个credits，除了发送buffer数据，同时还会携带当前的backlog信息（指的是当前这个subparititon还有多少buffer在等待发送），接收端拿到这个信息之后就会去申请相应的合适数量的floating buffer来处理subpartition中排队等待处理的buffer。接收端一般会申请和backlog一样多的buffer，但这个并不总是能申请到这么多，也可能当前根本没有buffer可以申请。接收端将会通过监听buffer池，等待buffer使用完回收的时候就可以开始新的buffer申请</p>
<p><img src="https://flink.apache.org/img/blog/2019-06-05-network-stack/flink-network-stack4.png" alt></p>
<p>流控模式下使用<code>buffers-per-channel</code>来指定每一个channel的独占buffer的大小，使用<code>floating-buffers-per-gate</code>来指定local buffer pool的大小这两个参数的默认值理论上可以达到和非流控模式下的最大吞吐量，可能你需要根据你的网络带宽或者rt要求来调整相关的参数</p>
<h3 id="Inflicting-Backpressure-2"><a href="#Inflicting-Backpressure-2" class="headerlink" title="Inflicting Backpressure (2)"></a>Inflicting Backpressure (2)</h3><p>和没有流控的模式相比，credits能够提供更加直接有效的控制： 如果消费端无法快速处理造成消费端的buffer用尽，这样该recevier的credits就会降为0，发送端就不会再发送数据到这个partition。反压只发生在了这个通道上，并不会影响tcp通道的数据传输，因此其他接收端的消费能力并不会受到影响。</p>
<h3 id="What-do-we-Gain-Where-is-the-Catch"><a href="#What-do-we-Gain-Where-is-the-Catch" class="headerlink" title="What do we Gain? Where is the Catch?"></a>What do we Gain? Where is the Catch?</h3><p>通过这样的优化整体的资源使用率应该会得到上升，并且通过对正在传输的数据量的直接控制，也带来了checkpoint对齐时间的优化。但是receiver端发送消息也带来了额外的开销（在这之前接收端是只要负责收数据就好了，没有发消息的动作），尤其是在开启了SSL加密的情况下。并且一个input channel无法使用所有的buffer pool，因为独占的buffer无法共享。而且如果你产出数据比你发布credits慢，就会导致无法尽可能快的下发数据，虽然这些情况会带来性能上的损失，但是通常还是建议开启流控的模式，因为带来的诸多好处。</p>
<p>另一件你可能会注意到的是因为我们在发送端和接收端之间会缓存更少的数据可能会更早的碰到反压，可以通过调节上述的exclusive和floating buffer的大小来缓解</p>
<h3 id="Writing-Records-into-Network-Buffers-and-Reading-them-again"><a href="#Writing-Records-into-Network-Buffers-and-Reading-them-again" class="headerlink" title="Writing Records into Network Buffers and Reading them again"></a>Writing Records into Network Buffers and Reading them again</h3><p>下图对上面的图做了一些扩展，添加了一些周边组件使用的一些细节</p>
<p><img src="https://flink.apache.org/img/blog/2019-06-05-network-stack/flink-network-stack6.png" alt></p>
<p>在创建一个record将其传递给下游，比如通过<code>Collector#collect()</code>,它将被传递到RecordWriter中，recordwriter将这个java对象序列化成二进制数组，最终被拷贝至networkbuffer中像上述几节中的处理方式进行处理。RecordWriter首先将数据通过SpanningRecordWriter序列化到对上的一个byte数组中。然后将会将这些byte数组写到相应目标channel的的network buffer中，我们再最后一小节再回来讨论这块细节。</p>
<p>在接收端，netty会将接收到的buffer写入相应的input channel，流式任务的task最后将会读取这些队列中的buffer，将其通过RecordReader反序列化出来，和序列化过程类似，反序列也需要处理一些特殊情况，比如一条记录跨过了多个network buffer，可能是因为一条记录比较大，大过了单个networkbuffer大小（32K），也有可能数据被加入networkbuffer的时候已经没有足够的容纳空间了。</p>
<h3 id="Flushing-Buffers-to-Netty"><a href="#Flushing-Buffers-to-Netty" class="headerlink" title="Flushing Buffers to Netty"></a>Flushing Buffers to Netty</h3><p>在上图中，流控的机制实际上就是在NettyServer和NettyClient组件中实现的，RecordWriter正在写的buffer总是最开始是空的没有数据的状态被加入到result subpatition中，然后再渐渐的被序列化的记录填满，但是netty是什么时候真正处理这些buffer呢？</p>
<p>显然他不能一有数据就去请求，因为这会带来巨大的开销，涉及到跨线程通信和同步，并且也会将整个buffer废弃掉。</p>
<p>在Flink中有3个情况可以将buffer变为NettyServer可以消费的状态：</p>
<ul>
<li>buffer写满了</li>
<li>buffer timeout时间条件满足了</li>
<li>一个特殊的event发送了，比如checkpoint barrier，为了保证一致性就需要发送</li>
</ul>
<h3 id="Flush-after-Buffer-Full"><a href="#Flush-after-Buffer-Full" class="headerlink" title="Flush after Buffer Full"></a>Flush after Buffer Full</h3><p>在序列化完成后，RecordWriter会将这些bytes写出到合适的subpartition的network buffer队列中去，尽管一个RecordWriter可以处理多个subpartition，但是每一个subpartition都只会有一个writer向其写数据。NettyServer会从多个subpartition读取buffer，通过一个channel发送出去。这是经典的生产者和消费者模式，正如下图所示。在(1)序列化和(2)将数据写出到buffer，RecordWriter会更新buffer的writer下标，一旦buffer已经满了，RecordWriter会从他的local buffer pool中申请一块新的buffer用来写当前记录的剩余的bytes，或者写下一条记录，并且将新的buffer添加搭配subpartition的queue中。 （4）通知NettyServer数据已经ready，当netty能够发送数据时，就会（5）从queue中获取buffer，通过TCP channel将数据发送给下游。</p>
<p><img src="https://flink.apache.org/img/blog/2019-06-05-network-stack/flink-network-stack7.png" alt></p>
<h3 id="Flush-after-Buffer-Timeout"><a href="#Flush-after-Buffer-Timeout" class="headerlink" title="Flush after Buffer Timeout"></a>Flush after Buffer Timeout</h3><p>为了能够支持低延迟的处理场景，我们不能只依赖buffer变满的下发信号。可能会由于上游某些通道数据不多带了发送延迟。因此还有周期性的发送机制：<code>OutputFlusher</code>。下图展示了这个flusher机制是如何和其他组件协同工作的。</p>
<p>RecordWriter进行序列化并将数据写入network buffer。但是并行的output flusher（3，4）会通知NettyServer进行数据的消费。当NettyServer收到通知之后，他将会消费buffer中可以消费的数据，并且更新buffer的reader index。buffer仍然会保留在队列中，下一次对该buffer的读取操作会从上次的reader index开始。严格的说flusher并没有保障数据的发送，他仅仅是通知NettyServer可以进行数据发送，如果正处于反压状态，flusher并没有什么实际效果</p>
<p><img src="https://flink.apache.org/img/blog/2019-06-05-network-stack/flink-network-stack8.png" alt></p>
<h3 id="Flush-after-special-event"><a href="#Flush-after-special-event" class="headerlink" title="Flush after special event"></a>Flush after special event</h3><p>一些特殊消息也会触发数据的flush，最重要的就是checkpoint barrier和end-of-stream</p>
<h3 id="Future-remarks"><a href="#Future-remarks" class="headerlink" title="Future remarks"></a>Future remarks</h3><p>和Flink1.5之前相比，network buffers现在被放置在了subpartition的队列中，而且每次flush我们并不会关闭buffer，这给我们带来一些好处：</p>
<ul>
<li>减少了同步带来的消耗（output flusher和RecordWriter是相互独立的）</li>
<li>在负载高，Netty是性能瓶颈的情况下我们仍然可以在不完整的buffer中累积数据</li>
<li>减少了netty的通知信号</li>
</ul>
<p>但是你可能也注意到在低负载的场景下，cpu使用率和TCP发包率会变高，这是因为flink会利用cpu来达到想要的低延迟，在高负载情况下性能可能会更好一些，因为去除了一些同步的消耗。</p>
<h3 id="Buffer-Builder-amp-Buffer-Consumer"><a href="#Buffer-Builder-amp-Buffer-Consumer" class="headerlink" title="Buffer Builder &amp; Buffer Consumer"></a>Buffer Builder &amp; Buffer Consumer</h3><p>这一节可以参考我之前博客中的相关分析<a href="http://aitozi.com/flink-network-feature.html#BufferBuilder%EF%BC%8CBufferConsumer%EF%BC%8CPositionMarker" target="_blank" rel="noopener">BufferBuiler/BufferConsumer</a></p>
<h3 id="Latency-vs-Throughput"><a href="#Latency-vs-Throughput" class="headerlink" title="Latency vs. Throughput"></a>Latency vs. Throughput</h3><p>Network buffers是被使用来以期望达到更高的资源利用率，并且让数据再buffer中等待一段时间来攒批发送来达到更高的吞吐。尽管这个等待时长可以通过设置buffer timeout时间。你可能会好奇想找出latency和thoughout之间的平衡关系。显然，你是不可能同时拥有这两者的。下图显示了不同的time out时间设置，0ms-100ms所带来的吞吐量的提升，这些测试是跑在100个就节点，每个节点8个slots，没有业务逻辑只有纯粹的网络栈开销。</p>
<p><img src="https://flink.apache.org/img/blog/2019-06-05-network-stack/flink-network-stack9.png" alt></p>
<p>如你所见，Flink1.5+，即使是非常低的缓冲区超时（例如1ms）也提供高达默认超时的75％的最大吞吐量。</p>
<p>参考: <a href="https://flink.apache.org/2019/06/05/flink-network-stack.html" target="_blank" rel="noopener">https://flink.apache.org/2019/06/05/flink-network-stack.html</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;[toc]&lt;/p&gt;
&lt;p&gt;本文翻译自flink官网的一篇博文，详细介绍Flink架构中的网络栈，下面就让我们来一睹为快吧。&lt;/p&gt;
    
    </summary>
    
      <category term="深度好文" scheme="http://www.aitozi.com/categories/%E6%B7%B1%E5%BA%A6%E5%A5%BD%E6%96%87/"/>
    
    
      <category term="Flink" scheme="http://www.aitozi.com/tags/Flink/"/>
    
  </entry>
  
  <entry>
    <title>Jmh测试框架和flink benchmark工程</title>
    <link href="http://www.aitozi.com/jmh-usage.html"/>
    <id>http://www.aitozi.com/jmh-usage.html</id>
    <published>2019-04-14T13:05:46.000Z</published>
    <updated>2019-04-14T15:38:01.797Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p>
<p>本文着重介绍对jmh框架的理解和使用，以及在<a href="https://github.com/dataArtisans/flink-benchmarks," title="flink-benchmark" target="_blank" rel="noopener">flink-benchmark</a>中的应用。</p>
<a id="more"></a>
<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>JMH是一个由OpenJDK/Oracle里面那群开发了Java编译器的大牛们所开发的Micro Benchmark Framework。何谓Micro Benchmark呢？简单地说就是在method层面上的 benchmark，精度可以精确到微秒级。可以看出JMH主要使用在当你已经找出了热点函数，而需要对热点函数进行进一步的优化时，就可以使用JMH对优化的效果进行定量的分析。在flink-benchmark框架中主要用来对network和state进行定量分析，来确保每次对这两个模块的修改不会导致性能的regress.</p>
<p><a href="http://apache-flink-mailing-list-archive.1008284.n3.nabble.com/Codespeed-deployment-for-Flink-td24274.html" target="_blank" rel="noopener">http://apache-flink-mailing-list-archive.1008284.n3.nabble.com/Codespeed-deployment-for-Flink-td24274.html</a></p>
<p>比较典型的使用场景有：</p>
<p>想定量地知道某个函数需要执行多长时间，以及执行时间和输入n的相关性,一个函数有两种不同实现（例如实现A使用了FixedThreadPool，实现B使用了ForkJoinPool），不知道哪种实现性能更好.</p>
<h3 id="参数含义"><a href="#参数含义" class="headerlink" title="参数含义"></a>参数含义</h3><p>测试case: </p>
<p><a href="https://github.com/Aitozi/test-case/blob/68a6d5dbc28220cb54b8057d147632f3aed6bd31/src/main/java/jmh/SimpleBenchT.java" target="_blank" rel="noopener">https://github.com/Aitozi/test-case/blob/68a6d5dbc28220cb54b8057d147632f3aed6bd31/src/main/java/jmh/SimpleBenchT.java</a></p>
<h4 id="BenchmarkMode"><a href="#BenchmarkMode" class="headerlink" title="@BenchmarkMode"></a>@BenchmarkMode</h4><p>基准测试类型。这里选择的是Throughput也就是吞吐量。根据源码点进去，每种类型后面都有对应的解释，比较好理解，吞吐量会得到单位时间内可以进行的操作数。</p>
<ul>
<li>Throughput: 整体吞吐量，例如“1秒内可以执行多少次调用”。</li>
<li>AverageTime: 调用的平均时间，例如“每次调用平均耗时xxx毫秒”。</li>
<li>SampleTime: 随机取样，最后输出取样结果的分布，例如“99%的调用在xxx毫秒以内，99.99%的调用在xxx毫秒以内”</li>
<li>SingleShotTime: 以上模式都是默认一次iteration是1s，唯有SingleShotTime是只运行一次。往往同时把warmup次数设为0，用于测试冷启动时的性能。</li>
<li>All 执行所有的类型测试</li>
</ul>
<h4 id="Warmup"><a href="#Warmup" class="headerlink" title="@Warmup"></a>@Warmup</h4><p>进行基准测试前需要进行预热。一般我们前几次进行程序测试的时候都会比较慢， 所以要让程序进行几轮预热，保证测试的准确性。其中的参数iterations也就非常好理解了，就是预热轮数。</p>
<p>为什么需要预热？因为JVM的JIT机制的存在，如果某个函数被调用多次之后，JVM会尝试将其编译成为机器码从而提高执行速度。所以为了让benchmark的结果更加接近真实情况就需要进行预热。</p>
<h4 id="Measurement"><a href="#Measurement" class="headerlink" title="@Measurement"></a>@Measurement</h4><p>测试参数</p>
<ul>
<li>iterations 进行测试的轮次</li>
<li>time 每轮进行的时长</li>
<li>timeUnit 时长单位</li>
</ul>
<p>都是一些基本的参数，可以根据具体情况调整。一般比较重的东西可以进行大量的测试，放到服务器上运行。</p>
<h4 id="Threads"><a href="#Threads" class="headerlink" title="@Threads"></a>@Threads</h4><p>每个进程中的测试线程，这个非常好理解，根据具体情况选择，一般为cpu乘以2。</p>
<h4 id="Fork"><a href="#Fork" class="headerlink" title="@Fork"></a>@Fork</h4><p>进行fork的次数。如果fork数是2的话，则JMH会fork出两个进程来进行测试。</p>
<h4 id="OutputTimeUnit"><a href="#OutputTimeUnit" class="headerlink" title="@OutputTimeUnit"></a>@OutputTimeUnit</h4><p>这个比较简单了，基准测试结果的时间类型。一般选择秒、毫秒、微秒。</p>
<h4 id="Benchmark"><a href="#Benchmark" class="headerlink" title="@Benchmark"></a>@Benchmark</h4><p>方法级注解，表示该方法是需要进行benchmark的对象，用法和JUnit的@Test类似。</p>
<h4 id="Param"><a href="#Param" class="headerlink" title="@Param"></a>@Param</h4><p>属性级注解，@Param可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。</p>
<h4 id="Setup"><a href="#Setup" class="headerlink" title="@Setup"></a>@Setup</h4><p>方法级注解，这个注解的作用就是我们需要在测试之前进行一些准备工作，比如对一些数据的初始化之类的。</p>
<h4 id="TearDown"><a href="#TearDown" class="headerlink" title="@TearDown"></a>@TearDown</h4><p>方法级注解，这个注解的作用就是我们需要在测试之后进行一些结束工作，比如关闭线程池，数据库连接等的，主要用于资源的回收等。</p>
<h4 id="State"><a href="#State" class="headerlink" title="@State"></a>@State</h4><p>当使用@Setup参数的时候，必须在类上加这个参数，不然会提示无法运行。</p>
<p>State用于声明某个类是一个“状态”，然后接受一个Scope参数用来表示该状态的共享范围。 因为很多benchmark会需要一些表示状态的类，JMH允许你把这些类以依赖注入的方式注入到 benchmark函数里。Scope主要分为三种。</p>
<p>Thread: 该状态为每个线程独享。<br>Group: 该状态为同一个组里面所有线程共享。<br>Benchmark: 该状态在所有线程间共享。</p>
<p>关于State的用法，官方的<a href="http://hg.openjdk.java.net/code-tools/jmh/file/cb9aa824b55a/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_03_States.java," title="sample" target="_blank" rel="noopener">code sample</a>里有比较好的例子。</p>
<h4 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h4><ul>
<li><p>CompilerControl控制 compiler 的行为，例如强制 inline，不允许编译等。</p>
</li>
<li><p>Group 可以把多个 benchmark 定义为同一个 group，则它们会被同时执行，主要用于测试多个相互之间存在影响的方法。</p>
</li>
<li><p>Level 用于控制 @Setup，@TearDown 的调用时机，默认是 Level.Trial，即benchmark开始前和结束后。</p>
</li>
<li><p>Profiler JMH 支持一些 profiler，可以显示等待时间和运行时间比，热点函数等。</p>
</li>
</ul>
<h3 id="flink-benchmark"><a href="#flink-benchmark" class="headerlink" title="flink benchmark"></a>flink benchmark</h3><h4 id="serialization"><a href="#serialization" class="headerlink" title="serialization"></a>serialization</h4><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><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">@Benchmark</span><br><span class="line">@OperationsPerInvocation(value = RECORDS_PER_INVOCATION) </span><br><span class="line">/**</span><br><span class="line">* 告诉jmh该测试方法内部会执行RECORDS_PER_INVOCATION次数，最后计算score的时候需要</span><br><span class="line">* 把这个考虑上.http://javadox.com/org.openjdk.jmh/jmh-core/0.9/org/openjdk/jmh/annotations/OperationsPerInvocation.html</span><br><span class="line">/</span><br><span class="line">public void serializerKryoWithoutRegistration() throws Exception &#123;</span><br><span class="line">	LocalStreamEnvironment env =</span><br><span class="line">			StreamExecutionEnvironment.createLocalEnvironment(4);</span><br><span class="line">	env.getConfig().enableForceKryo();</span><br><span class="line"></span><br><span class="line">	env.addSource(new PojoSource(RECORDS_PER_INVOCATION, 10))</span><br><span class="line">			.rebalance()</span><br><span class="line">			.addSink(new DiscardingSink&lt;&gt;());</span><br><span class="line"></span><br><span class="line">	env.execute();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>以及： <code>SerializationFrameworkMiniBenchmarks</code></p>
<h4 id="keyby"><a href="#keyby" class="headerlink" title="keyby"></a>keyby</h4><p>测试在tuple和array上的keyby性能</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><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">@Benchmark</span><br><span class="line">@OperationsPerInvocation(value = KeyByBenchmarks.TUPLE_RECORDS_PER_INVOCATION)</span><br><span class="line">public void tupleKeyBy() throws Exception &#123;</span><br><span class="line">	LocalStreamEnvironment env =</span><br><span class="line">			StreamExecutionEnvironment.createLocalEnvironment(4);</span><br><span class="line"></span><br><span class="line">	env.addSource(new IncreasingTupleSource(TUPLE_RECORDS_PER_INVOCATION, 10))</span><br><span class="line">			.keyBy(0)</span><br><span class="line">			.addSink(new DiscardingSink&lt;&gt;());</span><br><span class="line"></span><br><span class="line">	env.execute();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@Benchmark</span><br><span class="line">@OperationsPerInvocation(value = KeyByBenchmarks.ARRAY_RECORDS_PER_INVOCATION)</span><br><span class="line">public void arrayKeyBy() throws Exception &#123;</span><br><span class="line">	LocalStreamEnvironment env =</span><br><span class="line">			StreamExecutionEnvironment.createLocalEnvironment(4);</span><br><span class="line"></span><br><span class="line">	env.addSource(new IncreasingArraySource(ARRAY_RECORDS_PER_INVOCATION, 10))</span><br><span class="line">			.keyBy(0)</span><br><span class="line">			.addSink(new DiscardingSink&lt;&gt;());</span><br><span class="line"></span><br><span class="line">	env.execute();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="state-backend"><a href="#state-backend" class="headerlink" title="state backend"></a>state backend</h4><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></pre></td><td class="code"><pre><span class="line"> source</span><br><span class="line">.map(new MultiplyIntLongByTwo())</span><br><span class="line">.keyBy(record -&gt; record.key)</span><br><span class="line">.window(windowAssigner)</span><br><span class="line">.reduce(new SumReduceIntLong())</span><br><span class="line">.addSink(new CollectSink());</span><br></pre></td></tr></table></figure>
<p>利用window窗口聚合，对不同backend做不同的基准测试，测试的和state api不够直接</p>
<h4 id="window"><a href="#window" class="headerlink" title="window"></a>window</h4><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><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">@Benchmark</span><br><span class="line">public void tumblingWindow(TimeWindowContext context) throws Exception &#123;</span><br><span class="line">	IntLongApplications.reduceWithWindow(context.source, TumblingEventTimeWindows.of(Time.seconds(10_000)));</span><br><span class="line">	context.execute();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@Benchmark</span><br><span class="line">public void slidingWindow(TimeWindowContext context) throws Exception &#123;</span><br><span class="line">	IntLongApplications.reduceWithWindow(context.source, SlidingEventTimeWindows.of(Time.seconds(10_000), Time.seconds(1000)));</span><br><span class="line">	context.execute();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@Benchmark</span><br><span class="line">public void sessionWindow(TimeWindowContext context) throws Exception &#123;</span><br><span class="line">	IntLongApplications.reduceWithWindow(context.source, EventTimeSessionWindows.withGap(Time.seconds(500)));</span><br><span class="line">	context.execute();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="network"><a href="#network" class="headerlink" title="network"></a>network</h4><p>测试吞吐和延迟:</p>
<ul>
<li>StreamNetworkThroughputBenchmarkExecutor</li>
<li>StreamNetworkLatencyBenchmarkExecutor</li>
</ul>
<h4 id="state-operations"><a href="#state-operations" class="headerlink" title="state operations"></a>state operations</h4><p><a href="https://github.com/dataArtisans/flink-benchmarks/pull/13" target="_blank" rel="noopener">https://github.com/dataArtisans/flink-benchmarks/pull/13</a></p>
<p>###参考</p>
<p><a href="https://www.xncoding.com/2018/01/07/java/jmh.html" target="_blank" rel="noopener">https://www.xncoding.com/2018/01/07/java/jmh.html</a><br><a href="http://tutorials.jenkov.com/java-performance/jmh.html" target="_blank" rel="noopener">http://tutorials.jenkov.com/java-performance/jmh.html</a><br><a href="https://www.cnkirito.moe/java-jmh/" target="_blank" rel="noopener">https://www.cnkirito.moe/java-jmh/</a> 详细介绍平常测试代码中的陷阱</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;[toc]&lt;/p&gt;
&lt;p&gt;本文着重介绍对jmh框架的理解和使用，以及在&lt;a href=&quot;https://github.com/dataArtisans/flink-benchmarks,&quot; title=&quot;flink-benchmark&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;flink-benchmark&lt;/a&gt;中的应用。&lt;/p&gt;
    
    </summary>
    
      <category term="性能优化" scheme="http://www.aitozi.com/categories/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    
      <category term="Flink" scheme="http://www.aitozi.com/tags/Flink/"/>
    
      <category term="benchmark" scheme="http://www.aitozi.com/tags/benchmark/"/>
    
      <category term="jmh" scheme="http://www.aitozi.com/tags/jmh/"/>
    
  </entry>
  
  <entry>
    <title>Binary Row数据结构的实现</title>
    <link href="http://www.aitozi.com/BinaryRow-implement.html"/>
    <id>http://www.aitozi.com/BinaryRow-implement.html</id>
    <published>2019-03-30T17:53:46.000Z</published>
    <updated>2019-03-31T01:05:55.866Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p>
<p>Binary Row是blink开源版本<a href="https://github.com/apache/flink/tree/blink" target="_blank" rel="noopener">https://github.com/apache/flink/tree/blink</a>中提到的一个runtime层面优化的特性，主要是应用于sql模块，简单来说，由于sql本身自带schema，在上下游数据传输的时候就可以利用这个schema信息来简化序列化和反序列化的过程，本文就来具体分析这个特性的实现。</p>
<a id="more"></a>
<p>主要实现代码在</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></pre></td><td class="code"><pre><span class="line">flink-table org.apache.flink.table.typeutils</span><br><span class="line">flink-table-common org.apache.flink.table.dataformat</span><br><span class="line">flink-table-common org.apache.flink.table.typeutils</span><br></pre></td></tr></table></figure>
<h3 id="Binary-Row"><a href="#Binary-Row" class="headerlink" title="Binary Row"></a>Binary Row</h3><p>我们要理解sql层的数据传输是用的什么结构，只需要去观察runtime层实现的算子的传输数据类型即可，通过查看代码可以发现中间算子传输的均为<code>BaseRow</code></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">public TwoInputSelection processElement1(StreamRecord&lt;BaseRow&gt; element) throws Exception &#123;&#125;</span><br></pre></td></tr></table></figure>
<p>而之前版本的数据传输的是一个Row,内部是一个<code>Object[]</code>，在传输的过程中使用<code>RowSerializer</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><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">public void serialize(Row record, DataOutputView target) throws IOException &#123;</span><br><span class="line">	int len = fieldSerializers.length;</span><br><span class="line"></span><br><span class="line">	if (record.getArity() != len) &#123;</span><br><span class="line">		throw new RuntimeException(&quot;Row arity of from does not match serializers.&quot;);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	// write a null mask</span><br><span class="line">	writeNullMask(len, record, target);</span><br><span class="line"></span><br><span class="line">	// serialize non-null fields</span><br><span class="line">	for (int i = 0; i &lt; len; i++) &#123;</span><br><span class="line">		Object o = record.getField(i);</span><br><span class="line">		if (o != null) &#123;</span><br><span class="line">			fieldSerializers[i].serialize(o, target);</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>新的实现中以BaseRow代替了Row，baserow是一个基类，在不同的场景下有不同的子类去实现相应的功能。</p>
<h4 id="GenericRow"><a href="#GenericRow" class="headerlink" title="GenericRow"></a>GenericRow</h4><p>能够方便的用以更新字段，其内部实现也是一个Object数组，</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">// kafka source deserialization schema 从source处就解析成一个BaseRow</span><br><span class="line">public GenericRow deserialize(byte[] messageKey, byte[] message, String topic, int partition, long offset) throws IOException &#123;</span><br><span class="line">	GenericRow row = new GenericRow(5);</span><br><span class="line">	row.update(0, messageKey);</span><br><span class="line">	row.update(1, message);</span><br><span class="line">	row.update(2, BinaryString.fromString(topic));</span><br><span class="line">	row.update(3, partition);</span><br><span class="line">	row.update(4, offset);</span><br><span class="line">	return row;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="JoinedRow"><a href="#JoinedRow" class="headerlink" title="JoinedRow"></a>JoinedRow</h4><p>主要能够方便的将两个row进行拼接成一个baserow</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">// windowoperator中发送一个key和aggRes的组合的row到下游</span><br><span class="line">reuseOutput.replace((BaseRow) getCurrentKey(), aggResult);</span><br></pre></td></tr></table></figure>
<h4 id="BinaryRow"><a href="#BinaryRow" class="headerlink" title="BinaryRow"></a>BinaryRow</h4><p>BaseRow序列化是先转化成BinaryRow，然后再通过BinaryRowSerializer进行序列化</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><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">// BaseRowSerializer.java</span><br><span class="line">public void serialize(BaseRow row, DataOutputView target) throws IOException &#123;</span><br><span class="line">	BinaryRow binaryRow;</span><br><span class="line">	if (row.getClass() == BinaryRow.class) &#123;</span><br><span class="line">		binaryRow = (BinaryRow) row;</span><br><span class="line">	&#125; else &#123;</span><br><span class="line">		binaryRow = baseRowToBinary(row);</span><br><span class="line">	&#125;</span><br><span class="line">	binarySerializer.serialize(binaryRow, target);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public BinaryRow baseRowToBinary(BaseRow baseRow) throws IOException &#123;</span><br><span class="line">	BinaryRow row = getProjection().apply(baseRow);</span><br><span class="line">	row.setHeader(baseRow.getHeader());</span><br><span class="line">	return row;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到baseToRow的过程中首先是会通过codeGen生成映射函数，然后将baserow转成binaryrow，测试将以下的GenericRow转化成BinaryRow生成如下代码</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">GenericRow gR = new GenericRow(3);</span><br><span class="line">gR.update(0, 1);</span><br><span class="line">gR.update(1, 2L);</span><br><span class="line">gR.update(2, &quot;test&quot;);</span><br><span class="line"></span><br><span class="line">BaseRowSerializer&lt;GenericRow&gt; serializer = new BaseRowSerializer&lt;&gt;(Types.INT, Types.LONG, Types.STRING);</span><br><span class="line">BinaryRow row = serializer.baseRowToBinary(gR);</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><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></pre></td><td class="code"><pre><span class="line">public class BaseRowSerializerProjection$0 extends org.apache.flink.table.codegen.Projection&lt;org.apache.flink.table.dataformat.BaseRow, org.apache.flink.table.dataformat.BinaryRow&gt; &#123;</span><br><span class="line"></span><br><span class="line">        org.apache.flink.table.dataformat.BinaryString reuseBString$3 = new org.apache.flink.table.dataformat.BinaryString();</span><br><span class="line">        final org.apache.flink.table.dataformat.BinaryRow out = new org.apache.flink.table.dataformat.BinaryRow(3);</span><br><span class="line">        // 先构建一个BinaryRowWriter</span><br><span class="line">        final org.apache.flink.table.dataformat.BinaryRowWriter outWriter = new org.apache.flink.table.dataformat.BinaryRowWriter(out);</span><br><span class="line"></span><br><span class="line">        public BaseRowSerializerProjection$0() throws Exception &#123;</span><br><span class="line">          </span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public org.apache.flink.table.dataformat.BinaryRow apply(org.apache.flink.table.dataformat.BaseRow in1) &#123;</span><br><span class="line">          int field$1;</span><br><span class="line">          boolean isNull$1;</span><br><span class="line">          long field$2;</span><br><span class="line">          boolean isNull$2;</span><br><span class="line">          org.apache.flink.table.dataformat.BinaryString field$4;</span><br><span class="line">          boolean isNull$4;</span><br><span class="line">          outWriter.reset();</span><br><span class="line">          isNull$1 = in1.isNullAt(0);</span><br><span class="line">          field$1 = -1;</span><br><span class="line">          if (!isNull$1) &#123;</span><br><span class="line">            field$1 = in1.getInt(0);</span><br><span class="line">          &#125;</span><br><span class="line">          if (isNull$1) &#123;</span><br><span class="line">            outWriter.setNullAt(0);</span><br><span class="line">          &#125; else &#123;</span><br><span class="line">            outWriter.writeInt(0, field$1);</span><br><span class="line">          &#125;</span><br><span class="line">          isNull$2 = in1.isNullAt(1);</span><br><span class="line">          field$2 = -1L;</span><br><span class="line">          if (!isNull$2) &#123;</span><br><span class="line">            field$2 = in1.getLong(1);</span><br><span class="line">          &#125;</span><br><span class="line">          if (isNull$2) &#123;</span><br><span class="line">            outWriter.setNullAt(1);</span><br><span class="line">          &#125; else &#123;</span><br><span class="line">            outWriter.writeLong(1, field$2);</span><br><span class="line">          &#125;</span><br><span class="line">          isNull$4 = in1.isNullAt(2);</span><br><span class="line">          field$4 = org.apache.flink.table.dataformat.BinaryString.EMPTY_UTF8;</span><br><span class="line">          if (!isNull$4) &#123;</span><br><span class="line">            field$4 = in1.getBinaryString(2, reuseBString$3);</span><br><span class="line">          &#125;</span><br><span class="line">          if (isNull$4) &#123;</span><br><span class="line">            outWriter.setNullAt(2);</span><br><span class="line">          &#125; else &#123;</span><br><span class="line">            outWriter.writeBinaryString(2, field$4);</span><br><span class="line">          &#125;</span><br><span class="line">          outWriter.complete();</span><br><span class="line">          return out;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br></pre></td></tr></table></figure>
<p>通过codeGen的代码可以得出，对于普通的baserow，通过BinaryRowWriter，将baserow的每个字段写入到BinaryRow中，写入完成后，序列化的工作就都通过BinaryRowSerializer来完成。这样的好处有以下几个:</p>
<ul>
<li>如果某个中间算子只需要获取上游传输下来的某几个字段的值，那么只需要通过getXXX来直接获取，减少反序列化的量</li>
<li>如果中间结果不发生改变，只需要将binaryRow直接拷贝出去，也减少了序列化的量</li>
</ul>
<p>BinaryRow序列化的过程，可以看到就是直接的内存拷贝的过程</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><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">public void serialize(BinaryRow record, DataOutputView target) throws IOException &#123;</span><br><span class="line">	int sizeInBytes = record.getSizeInBytes();</span><br><span class="line">	target.writeInt(sizeInBytes);</span><br><span class="line">	int offset = record.getBaseOffset();</span><br><span class="line">	for (MemorySegment segment : record.getAllSegments()) &#123;</span><br><span class="line">		int remain = segment.size() - offset;</span><br><span class="line">		int copySize = remain &gt; sizeInBytes ? sizeInBytes : remain;</span><br><span class="line">		target.write(segment, offset, copySize);</span><br><span class="line"></span><br><span class="line">		sizeInBytes -= copySize;</span><br><span class="line">		offset = 0;</span><br><span class="line">	&#125;</span><br><span class="line">	if (sizeInBytes != 0) &#123;</span><br><span class="line">		throw new RuntimeException(&quot;No copy finished, this should be a bug, &quot; +</span><br><span class="line">				&quot;The remaining length is: &quot; + sizeInBytes);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="BinaryString"><a href="#BinaryString" class="headerlink" title="BinaryString"></a>BinaryString</h4><p>在上面codegen的一段代码中，关于string的处理引入了一个概念BinaryString</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></pre></td><td class="code"><pre><span class="line">field$4 = org.apache.flink.table.dataformat.BinaryString.EMPTY_UTF8;</span><br><span class="line">if (!isNull$4) &#123;</span><br><span class="line">  field$4 = in1.getBinaryString(2, reuseBString$3);</span><br><span class="line">&#125;</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><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">// GenericRow的getBinaryString的实现</span><br><span class="line">public BinaryString getBinaryString(int ordinal) &#123;</span><br><span class="line">	Object value = this.fields[ordinal];</span><br><span class="line">	if (value instanceof BinaryString) &#123;</span><br><span class="line">		return (BinaryString) value;</span><br><span class="line">	&#125; else &#123;</span><br><span class="line">		return BinaryString.fromString((String) value);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>那么BinaryString是什么作用呢? 看注释</p>
<blockquote>
<p>A utf8 string which is backed by {@link MemorySegment} instead of String. Its data may span multiple {@link MemorySegment}s.<br>一个直接存储在MemorySegment上的utf8的字符串，一个字符串数据可能会跨segment</p>
</blockquote>
<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></pre></td><td class="code"><pre><span class="line">private MemorySegment[] segments;</span><br><span class="line">private int offset;</span><br><span class="line">private int numBytes;</span><br><span class="line"></span><br><span class="line">/** Cache the java string for the binary string to avoid redundant decode. */</span><br><span class="line">private String javaString;</span><br></pre></td></tr></table></figure>
<p>针对string类型，会在codegen阶段，将其转化成一个binaryString。从binarystring初始化的时候没有存储在MemorySegment之上，而是仅仅只是保存string的字符串信息，等到有对string的操作的时候,才会通过这个方法将其序列化，并包装成memorysegments</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><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">public void ensureEncoded() &#123;</span><br><span class="line">	if (!isEncoded()) &#123;</span><br><span class="line">		encodeToBytes();</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">private void encodeToBytes() &#123;</span><br><span class="line">	if (javaString != null) &#123;</span><br><span class="line">		byte[] bytes = StringUtf8Utils.encodeUTF8(javaString);</span><br><span class="line">		pointTo(bytes, 0, bytes.length, javaString);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</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><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">public void serialize(BinaryString record, DataOutputView target) throws IOException &#123;</span><br><span class="line">	byte[] bytes = record.getBytes();</span><br><span class="line">	target.writeInt(bytes.length);</span><br><span class="line">	target.write(bytes);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * Maybe not copied, if want copy, please use copyTo.</span><br><span class="line"> */</span><br><span class="line">public static byte[] getBytes(MemorySegment[] segments, int baseOffset, int sizeInBytes) &#123;</span><br><span class="line">	// avoid copy if `base` is `byte[]`</span><br><span class="line">	if (segments.length == 1) &#123;</span><br><span class="line">		byte[] heapMemory = segments[0].getHeapMemory();</span><br><span class="line">		// 基于byte[]数组的memorysegment</span><br><span class="line">		if (baseOffset == 0</span><br><span class="line">				&amp;&amp; heapMemory != null</span><br><span class="line">				&amp;&amp; heapMemory.length == sizeInBytes) &#123;</span><br><span class="line">			return heapMemory;</span><br><span class="line">		&#125; else &#123;</span><br><span class="line">			// 将内存从堆外内存拷贝出来</span><br><span class="line">			byte[] bytes = new byte[sizeInBytes];</span><br><span class="line">			segments[0].get(baseOffset, bytes, 0, sizeInBytes);</span><br><span class="line">			return bytes;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125; else &#123;</span><br><span class="line">		byte[] bytes = new byte[sizeInBytes];</span><br><span class="line">		BinaryRowUtil.copySlow(segments, baseOffset, bytes, 0, sizeInBytes);</span><br><span class="line">		return bytes;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>BinaryString有什么好处呢？</p>
<ol>
<li>仅仅序列化一次</li>
<li>是可以修改的string，而不会产生中间对象</li>
<li>序列化的时候仅仅是内存的拷贝</li>
</ol>
<h4 id="BinaryArray"><a href="#BinaryArray" class="headerlink" title="BinaryArray"></a>BinaryArray</h4><p>同样的基于memorysegment实现的还有BinaryMap和BinaryArray，这两者都有一个Generic的实现用以快速的更新, GenericArray要求<strong>数据类型都是相同的类型</strong></p>
<p>BinaryArray的存储格式:</p>
<blockquote>
<p>[numElements(int)] + [null bits(4-byte word boundaries)] + [values or offset&amp;length] + [variable length part].</p>
</blockquote>
<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><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">public void serialize(BaseArray record, DataOutputView target) throws IOException &#123;</span><br><span class="line">	BinaryArray binaryArray = baseArrayToBinary(record);</span><br><span class="line">	target.write(binaryArray.getBytes());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public BinaryArray baseArrayToBinary(BaseArray from) &#123;</span><br><span class="line">	if (from instanceof BinaryArray) &#123;</span><br><span class="line">		return (BinaryArray) from;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	int numElements = from.numElements();</span><br><span class="line">	if (reuseBinaryArray == null) &#123;</span><br><span class="line">		reuseBinaryArray = new BinaryArray();</span><br><span class="line">	&#125;</span><br><span class="line">	if (reuseBinaryWriter == null || reuseBinaryWriter.getNumElements() != numElements) &#123;</span><br><span class="line">		reuseBinaryWriter = new BinaryArrayWriter(</span><br><span class="line">			reuseBinaryArray, numElements, BinaryArray.calculateElementSize(eleType));</span><br><span class="line">	&#125; else &#123;</span><br><span class="line">		reuseBinaryWriter.reset();</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	for (int i = 0; i &lt; numElements; i++) &#123;</span><br><span class="line">		if (from.isNullAt(i)) &#123;</span><br><span class="line">			reuseBinaryWriter.setNullAt(i, eleType);</span><br><span class="line">		&#125; else &#123;</span><br><span class="line">			BaseRowUtil.write(reuseBinaryWriter, i,</span><br><span class="line">					TypeGetterSetters.get(from, i, eleType), eleType, elementSerializer);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	reuseBinaryWriter.complete();</span><br><span class="line"></span><br><span class="line">	return reuseBinaryArray;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>从BinaryArraySerializer可以看到，处理方式和BaseRow很像，先baseToBinary，然后直接从segments拷贝byte。</p>
<h4 id="DataStructureConverters"><a href="#DataStructureConverters" class="headerlink" title="DataStructureConverters"></a>DataStructureConverters</h4><p>以上的类型和这个类型convert搭配使用才发挥出相应的效果，这个工具类的作用在于在codegen的阶段，根据输入的类型去转化为相对应的InternalType</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><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">def genToInternal(ctx: CodeGeneratorContext, t: DataType): String =&gt; String = &#123;</span><br><span class="line">    val iTerm = boxedTypeTermForType(t.toInternalType)</span><br><span class="line">    val eTerm = externalBoxedTermForType(t)</span><br><span class="line">    if (isIdentity(t)) &#123;</span><br><span class="line">      term =&gt; s&quot;($iTerm) $term&quot;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      val scalarFuncTerm = classOf[BuildInScalarFunctions].getCanonicalName</span><br><span class="line">      TypeConverters.createExternalTypeInfoFromDataType(t) match &#123;</span><br><span class="line">        case Types.STRING =&gt; term =&gt; s&quot;$BINARY_STRING.fromString($term)&quot;</span><br><span class="line">        case Types.SQL_DATE | Types.SQL_TIME =&gt;</span><br><span class="line">          term =&gt; s&quot;$scalarFuncTerm.safeToInt(($eTerm) $term)&quot;</span><br><span class="line">        case Types.SQL_TIMESTAMP =&gt; term =&gt; s&quot;$scalarFuncTerm.safeToLong(($eTerm) $term)&quot;</span><br><span class="line">        case _ =&gt;</span><br><span class="line">          val converter = genConvertField(ctx, createToInternalConverter(t))</span><br><span class="line">          term =&gt; s&quot;($iTerm) $converter.apply($term)&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">// 生成的类型转化函数</span><br><span class="line">val internal = genToInternalIfNeeded(ctx, resultExternalType, resultClass, javaTerm)</span><br><span class="line">        s&quot;&quot;&quot;</span><br><span class="line">            |$javaTypeTerm $javaTerm = ($javaTypeTerm) $evalResult;</span><br><span class="line">            |$resultTerm = $javaTerm == null ? null : ($internal);</span><br><span class="line">            &quot;&quot;&quot;.stripMargin</span><br></pre></td></tr></table></figure>
<p>这样在codegen阶段就完成了相应类型的替换</p>
<h4 id="BinaryRow和BinaryArray的底层存储"><a href="#BinaryRow和BinaryArray的底层存储" class="headerlink" title="BinaryRow和BinaryArray的底层存储"></a>BinaryRow和BinaryArray的底层存储</h4><p>上面我们看到了binaryRow的使用方式，通过writeXXX的方式将数据写入到一个row中，一个row中可以写入基本类型，也可以写入binaryString, binaryArray，binaryMap等等变长的数据结构，其存储方式如下所示：</p>
<p><img src="https://github.com/Aitozi/images/blob/master/flink/flink-binaryrow.jpg?raw=true" alt="BinaryRow"></p>
<blockquote>
<p>A Row has two part: Fixed-length part and variable-length part.<br>Fixed-length part contains null bit set and field values. Null bit set is used for null tracking and is aligned to 8-byte word boundaries. <code>Field values</code> holds fixed-length primitive types and variable-length values which can be stored in 8 bytes inside. If it do not fit the variable-length field, then store the length and offset of variable-length part. Fixed-length part will certainly fall into a MemorySegment, which will speed up the read and write of field.<br>Variable-length part may fall into multiple MemorySegments.</p>
</blockquote>
<p>在将其他格式通过baseRowToBinaryRow的时候，确定了BinaryRow包含的field个数，</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></pre></td><td class="code"><pre><span class="line">public BinaryRowWriter(BinaryRow row, int initialSize) &#123;</span><br><span class="line">	this.nullBitsSizeInBytes = BinaryRow.calculateBitSetWidthInBytes(row.getArity());</span><br><span class="line">	this.fixedSize = row.getFixedLengthPartSize();</span><br><span class="line">	this.cursor = fixedSize;</span><br><span class="line"></span><br><span class="line">	this.segment = MemorySegmentFactory.wrap(new byte[fixedSize + initialSize]);</span><br><span class="line">	this.row = row;</span><br><span class="line">	this.row.pointTo(segment, 0, segment.size());</span><br><span class="line">&#125;</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><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public static int calculateBitSetWidthInBytes(int arity) &#123;</span><br><span class="line">	// add 8 bit header</span><br><span class="line">	return ((arity + 63 + 8) / 64) * 8;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这个函数是计算出null值得Flag位需要多少Byte来表示，这里的+63是将其对其到8的倍数（向上去整的意思），+8和spark代码相比其实是因为flink多了一个header存储回撤消息的标志位,null bits中第一个byte存储了header位的信息，这里的null bits的作用主要是用在runtime处理时可以快速判断一条数据是不是null值。</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></pre></td><td class="code"><pre><span class="line">public boolean anyNull() &#123;</span><br><span class="line">	// 这里有一个疑问，判断null的时候不是应该跳过第一个header位吗</span><br><span class="line">	for (int i = 0; i &lt; nullBitsSizeInBytes; i += 8) &#123;</span><br><span class="line">		if (segment.getLong(i) != 0) &#123;</span><br><span class="line">			return true;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	return false;</span><br><span class="line">&#125;</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><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public int getFixedLengthPartSize() &#123;</span><br><span class="line">	return nullBitsSizeInBytes + 8 * arity;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>获取整个固定长度字段的长度，再写变长区时就从这个offset写起。</p>
<p><strong>写定长数据</strong></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></pre></td><td class="code"><pre><span class="line">public void writeByte(int pos, byte value) &#123;</span><br><span class="line">	segment.put(getFieldOffset(pos), value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>写不定长的数据</strong><br><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">public void writeString(int pos, String input) &#123;</span><br><span class="line">	byte[] bytes = StringUtf8Utils.allocateBytes(input.length() * MAX_BYTES_PER_CHAR);</span><br><span class="line">	int len = StringUtf8Utils.encodeUTF8(input, bytes);</span><br><span class="line">	if (len &lt;= 7) &#123;</span><br><span class="line">		// 小于记录length的长度时直接写在固定长度区</span><br><span class="line">		writeLittleBytes(segment, getFieldOffset(pos), bytes, len);</span><br><span class="line">	&#125; else &#123;</span><br><span class="line">		writeBigBytes(pos, bytes, len);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>BinaryRow/BinaryRowWriter的实现和Spark中UnsafeRow/UnsafeRowWriter的实现非常相似，spark中的对此数据结构解释更为清晰一些。</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><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><br><span class="line"> * An Unsafe implementation of Row which is backed by raw memory instead of Java objects.</span><br><span class="line"> *</span><br><span class="line"> * Each tuple has three parts: [null bit set] [values] [variable length portion]</span><br><span class="line"> *</span><br><span class="line"> * The bit set is used for null tracking and is aligned to 8-byte word boundaries.  It stores</span><br><span class="line"> * one bit per field.</span><br><span class="line"> *</span><br><span class="line"> * In the `values` region, we store one 8-byte word per field. For fields that hold fixed-length</span><br><span class="line"> * primitive types, such as long, double, or int, we store the value directly in the word. For</span><br><span class="line"> * fields with non-primitive or variable-length values, we store a relative offset (w.r.t. the</span><br><span class="line"> * base address of the row) that points to the beginning of the variable-length field, and length</span><br><span class="line"> * (they are combined into a long).</span><br><span class="line"> *</span><br><span class="line"> */</span><br></pre></td></tr></table></figure>
<h3 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h3><p>看代码的时候遵循的是一种推理加源码追踪的手段，但是代码在初始设计的时候应该是另一种维度的思考，因此应该换一种思路去想如果自己来实现这个feature需要考虑什么地方，多多思考。</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;[toc]&lt;/p&gt;
&lt;p&gt;Binary Row是blink开源版本&lt;a href=&quot;https://github.com/apache/flink/tree/blink&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apache/flink/tree/blink&lt;/a&gt;中提到的一个runtime层面优化的特性，主要是应用于sql模块，简单来说，由于sql本身自带schema，在上下游数据传输的时候就可以利用这个schema信息来简化序列化和反序列化的过程，本文就来具体分析这个特性的实现。&lt;/p&gt;
    
    </summary>
    
      <category term="源码分析" scheme="http://www.aitozi.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
    
      <category term="Blink" scheme="http://www.aitozi.com/tags/Blink/"/>
    
  </entry>
  
  <entry>
    <title>flink sql与calcite</title>
    <link href="http://www.aitozi.com/flink-sql-tutorial.html"/>
    <id>http://www.aitozi.com/flink-sql-tutorial.html</id>
    <published>2019-03-25T08:45:30.000Z</published>
    <updated>2019-03-31T01:06:40.174Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p>
<p>基于Flink1.4.2版本分析flink与calcite结合构建的flink sql模块。</p>
<a id="more"></a>
<p>Flink SQL是现在Flink社区中着重发展的一个模块，我理解主要原因是因为</p>
<ol>
<li>SQL是一门发展很有的通用的描述性语言，接入门槛较低</li>
<li>有希望在sql层面实现流批计算的统一</li>
<li>能够通过sql优化器内置优化能力，避免需要每个用户方需要理解低阶任务的调优，屏蔽实现细节</li>
</ol>
<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>Flink SQL的总体执行流程为：</p>
<ul>
<li><em>SELECT</em>查询语句经过caclite parse成SqlNode</li>
<li>SqlNode经过validate校验</li>
<li>SqlNode经过calcite转化为relNode</li>
<li><em>Insert</em>语句将relNode经过calcite的优化和转化成FlinkRelNode</li>
<li>将相应的FlinkRelNode和codeGen生成的Function结合生成相应的执行算子</li>
</ul>
<p>下面以一个查询sql来讲解整体流程:</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><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">val stream = env</span><br><span class="line">        .fromCollection(data)</span><br><span class="line">        .assignTimestampsAndWatermarks(</span><br><span class="line">          new TimestampAndWatermarkWithOffset[(Long, String, String)](0L))</span><br><span class="line">val table = stream.toTable(tEnv, &apos;a, &apos;b, &apos;c, &apos;rowtime.rowtime)</span><br><span class="line"></span><br><span class="line">tEnv.registerTable(&quot;T1&quot;, table)</span><br><span class="line"></span><br><span class="line">val sqlQuery = &quot;SELECT c, COUNT(*), COUNT(1), COUNT(b) FROM T1 &quot; +</span><br><span class="line">  &quot;GROUP BY TUMBLE(rowtime, interval &apos;5&apos; SECOND), c&quot;</span><br><span class="line"></span><br><span class="line">val result = tEnv.sqlQuery(sqlQuery).toAppendStream[Row]</span><br><span class="line">result.addSink(new StreamITCase.StringSink[Row])</span><br><span class="line"></span><br><span class="line">env.execute()</span><br></pre></td></tr></table></figure>
<h3 id="parse"><a href="#parse" class="headerlink" title="parse"></a>parse</h3><h4 id="SqlNode"><a href="#SqlNode" class="headerlink" title="SqlNode"></a>SqlNode</h4><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">val parser: SqlParser = SqlParser.create(sql, parserConfig)</span><br><span class="line">val sqlNode: SqlNode = parser.parseStmt</span><br><span class="line">sqlNode</span><br></pre></td></tr></table></figure>
<p><img src="https://github.com/Aitozi/images/blob/master/flink/flink-sql-node.png?raw=true" alt="SqlNode" title="SqlNode"></p>
<p>SqlNode表示的是一颗sql解析树，由于Flink暂时只支持SELECT查询，所以我们这里得到的其实是一个<code>SqlSelect</code>实例，SqlSelect是一个SqlCall，SqlCall继承自SqlNode，每一个无叶子节点的节点就是一个Sqlcall，常见的SqlNode的子类就是，SqlKind是所有SqlNode类型的枚举类:</p>
<ul>
<li>SqlCall  表示一个树的无叶子节点的调用，例如图中的Count(*)</li>
<li>SqlNodeList 表示SqlNode的集合</li>
<li>SqlIdentifer 表示某个标识符</li>
</ul>
<h4 id="SqlOperator"><a href="#SqlOperator" class="headerlink" title="SqlOperator"></a>SqlOperator</h4><p>SqlNode的成员方法：</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">public List&lt;SqlNode&gt; getOperandList() &#123;</span><br><span class="line">  return ImmutableNullableList.of(keywordList, selectList, from, where,</span><br><span class="line">      groupBy, having, windowDecls, orderBy, offset, fetch);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public SqlOperator getOperator() &#123;</span><br><span class="line">  return SqlSelectOperator.INSTANCE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>getOperator返回的是这个是个什么操作，operands得到的运算对象。每一个SqlNode是由作用于一系列SqlNode的SqlOperator组成，SqlFunction也是一种SqlOperator. 这里SqlSelect node是SqlSelectOperator作用于以下的SqlNode节点</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">SqlNodeList keywordList;</span><br><span class="line">SqlNodeList selectList;</span><br><span class="line">SqlNode from;</span><br><span class="line">SqlNode where;</span><br><span class="line">SqlNodeList groupBy;</span><br><span class="line">SqlNode having;</span><br><span class="line">SqlNodeList windowDecls;</span><br><span class="line">SqlNodeList orderBy;</span><br><span class="line">SqlNode offset;</span><br><span class="line">SqlNode fetch;</span><br><span class="line">SqlMatchRecognize matchRecognize;</span><br></pre></td></tr></table></figure>
<h3 id="rel"><a href="#rel" class="headerlink" title="rel"></a>rel</h3><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">val rexBuilder: RexBuilder = createRexBuilder</span><br><span class="line">val cluster: RelOptCluster = FlinkRelOptClusterFactory.create(planner, rexBuilder)</span><br><span class="line">val config = SqlToRelConverter.configBuilder()</span><br><span class="line">  .withTrimUnusedFields(false).withConvertTableAccess(false).build()</span><br><span class="line">val sqlToRelConverter: SqlToRelConverter = new SqlToRelConverter(</span><br><span class="line">  new ViewExpanderImpl, validator, createCatalogReader, cluster, convertletTable, config)</span><br><span class="line">root = sqlToRelConverter.convertQuery(validatedSqlNode, false, true)</span><br></pre></td></tr></table></figure>
<p>这个过程是将SqlNode转化为RelNode的过程，RelNode表示关系型表达式,代表的是对数据的一个操作常见的有Project,Scan,Filter,Join等。通过explain可以看到相应的逻辑执行计划，以下还包括优化后的物理执行计划的一部分</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><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></pre></td><td class="code"><pre><span class="line"> == Abstract Syntax Tree ==</span><br><span class="line">LogicalProject(c=[$1], EXPR$1=[$2], EXPR$2=[$2], EXPR$3=[$3])</span><br><span class="line">  LogicalAggregate(group=[&#123;0, 1&#125;], EXPR$1=[COUNT()], EXPR$3=[COUNT($3)])</span><br><span class="line">    LogicalProject($f0=[TUMBLE($3, 5000)], c=[$2], $f2=[1], b=[$1])</span><br><span class="line">      LogicalTableScan(table=[[T1]])</span><br><span class="line"></span><br><span class="line">== Optimized Logical Plan ==</span><br><span class="line">DataStreamCalc(select=[c, EXPR$1, EXPR$1 AS EXPR$2, EXPR$3])</span><br><span class="line">  DataStreamGroupWindowAggregate(groupBy=[c], window=[TumblingGroupWindow(&apos;w$, &apos;rowtime, 5000.millis)], select=[c, COUNT(*) AS EXPR$1, COUNT(b) AS EXPR$3])</span><br><span class="line">    DataStreamCalc(select=[rowtime, c, 1 AS $f2, b])</span><br><span class="line">      DataStreamScan(table=[[_DataStreamTable_0]])</span><br><span class="line"></span><br><span class="line">== Physical Execution Plan ==</span><br><span class="line">Stage 1 : Data Source</span><br><span class="line">	content : collect elements with CollectionInputFormat</span><br><span class="line"></span><br><span class="line">	Stage 2 : Operator</span><br><span class="line">		content : Timestamps/Watermarks</span><br><span class="line">		ship_strategy : FORWARD</span><br><span class="line"></span><br><span class="line">		Stage 3 : Operator</span><br><span class="line">			content : from: (a, b, c, rowtime)</span><br><span class="line">			ship_strategy : FORWARD</span><br><span class="line"></span><br><span class="line">			Stage 4 : Operator</span><br><span class="line">				content : select: (rowtime, c, 1 AS $f2, b)</span><br><span class="line">				ship_strategy : FORWARD</span><br><span class="line"></span><br><span class="line">				Stage 5 : Operator</span><br><span class="line">					content : time attribute: (rowtime)</span><br><span class="line">					ship_strategy : FORWARD</span><br><span class="line"></span><br><span class="line">					Stage 7 : Operator</span><br><span class="line">						content : groupBy: (c), window: (TumblingGroupWindow(&apos;w$, &apos;rowtime, 5000.millis)), select: (c, COUNT(*) AS EXPR$1, COUNT(b) AS EXPR$3)</span><br><span class="line">						ship_strategy : HASH</span><br><span class="line"></span><br><span class="line">						Stage 8 : Operator</span><br><span class="line">							content : select: (c, EXPR$1, EXPR$1 AS EXPR$2, EXPR$3)</span><br><span class="line">							ship_strategy : FORWARD</span><br></pre></td></tr></table></figure>
<p>Flink sql查询的时候只做到这里的LogicalNode生成之后就完成了，等待sink才会触发下一步优化和转化逻辑。</p>
<h4 id="RelNode，RexNode"><a href="#RelNode，RexNode" class="headerlink" title="RelNode，RexNode"></a>RelNode，RexNode</h4><p>RelNode的实现类有LogicalProject，LogicalScan等表示的是数据处理方式，rexnode表示的是行表达式，是包含在一个RelNode中的,RexNode类中的exps字段就存储了相应的数据操作所需要的行表达式</p>
<p>参考以下讨论:</p>
<blockquote>
<p>Difference between sqlnode and relnode and rexnode<br><a href="https://www.mail-archive.com/dev@calcite.apache.org/msg01674.html" target="_blank" rel="noopener">https://www.mail-archive.com/dev@calcite.apache.org/msg01674.html</a></p>
</blockquote>
<h4 id="RexTraits-RelTraitDef"><a href="#RexTraits-RelTraitDef" class="headerlink" title="RexTraits, RelTraitDef"></a>RexTraits, RelTraitDef</h4><p>这个表示的是一个RelNode的物理特性，用于在convertRule中使用</p>
<p>在一个<code>ConverterRule</code>中的convert</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">val scan: FlinkLogicalNativeTableScan = rel.asInstanceOf[FlinkLogicalNativeTableScan]</span><br><span class="line">val traitSet: RelTraitSet = rel.getTraitSet.replace(FlinkConventions.DATASTREAM)</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><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">public RelTraitSet replace(</span><br><span class="line">    RelTrait trait) &#123;</span><br><span class="line">  // Quick check for common case</span><br><span class="line">  if (containsShallow(traits, trait)) &#123;</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line">  final RelTraitDef traitDef = trait.getTraitDef();</span><br><span class="line">  int index = findIndex(traitDef);</span><br><span class="line">  if (index &lt; 0) &#123;</span><br><span class="line">    // Trait is not present. Ignore it.</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return replace(index, trait);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>实际上是把某一类relTraitsDef的trait实现更换掉，相当于变更了RelNode的物理实现，planner转化过程应该只是trait的转化过程以及相应的RelNode的物理化的过程，按照论文中的解释: </p>
<blockquote>
<p>Traits. Calcite does not use different entities to represent logical<br>and physical operators. Instead, it describes the physical properties<br>associated with an operator using traits. These traits help the optimizer evaluate the cost of different alternative plans. Changing a<br>trait value does not change the logical expression being evaluated,<br>i.e., the rows produced by the given operator will still be the same</p>
</blockquote>
<p>因此在Flink中的转化<code>StreamTableEnvironment#optimize</code>,match之后根据HepPlanner和VocanoPlanner进行convert转化成物理算子，例如将</p>
<p><code>LogicalJoin(RelNode) -&gt; DataStreamJoin(FlinkRelNode) -&gt; translateToPlan -&gt; NonWindowJoin (runtime)</code></p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Flink SQL具体在flink中的实现分为LogicalPlan层，经过应用rule optimize之后的RelNode层，例如: DataStreamJoin, 再通过translate的时候code generator以及调用相应的runtime层的具体算子实现（这个对应的是physical plan的翻译），以上就完成了从SQL到Flink执行计划的翻译。从整个流程看calcite全程参与，使用方式非常的方便，足见整个calcite框架的扩展性做的很好。<br>Flink SQL中还有许多其他的细节：SQL中的回撤消息，join，distinct的具体通用算子的实现，还有sql优化，分流，ddl的实现，antlr的实现,窗口聚合等等这些实现细节后文再具体分析。</p>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p>介绍calcite与flink sql比较好的几篇文章：</p>
<p><a href="https://zhuanlan.zhihu.com/p/48735419" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/48735419</a><br><a href="https://arxiv.org/pdf/1802.10233.pdf" target="_blank" rel="noopener">https://arxiv.org/pdf/1802.10233.pdf</a>  calcite的论文<br><a href="https://zhuanlan.zhihu.com/p/51221350" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/51221350</a><br><a href="https://zhuanlan.zhihu.com/p/58249033" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/58249033</a><br><a href="https://zhuanlan.zhihu.com/p/59643962" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/59643962</a></p>
<hr>
<p><a href="http://matt33.com/2019/03/17/apache-calcite-planner/" target="_blank" rel="noopener">http://matt33.com/2019/03/17/apache-calcite-planner/</a><br><a href="http://matt33.com/2019/03/07/apache-calcite-process-flow/" target="_blank" rel="noopener">http://matt33.com/2019/03/07/apache-calcite-process-flow/</a><br><a href="https://www.slideshare.net/julianhyde/costbased-query-optimization-in-apache-phoenix-using-apache-calcite?qid=b7a1ca0f-e7bf-49ad-bc51-0615ec8a4971&amp;v=&amp;b=&amp;from_search=4" target="_blank" rel="noopener">https://www.slideshare.net/julianhyde/costbased-query-optimization-in-apache-phoenix-using-apache-calcite?qid=b7a1ca0f-e7bf-49ad-bc51-0615ec8a4971&amp;v=&amp;b=&amp;from_search=4</a></p>
<hr>
<p><a href="https://issues.apache.org/jira/browse/FLINK-7146" target="_blank" rel="noopener">https://issues.apache.org/jira/browse/FLINK-7146</a> Flink SQL DDL支持<br><a href="https://docs.google.com/document/d/1TTP-GCC8wSsibJaSUyFZ_5NBAHYEB1FVmPpP7RgDGBA/edit#heading=h.wpsqidkaaoil" target="_blank" rel="noopener">https://docs.google.com/document/d/1TTP-GCC8wSsibJaSUyFZ_5NBAHYEB1FVmPpP7RgDGBA/edit#heading=h.wpsqidkaaoil</a> doc</p>
<p><a href="https://github.com/TatianaJin/calcite_playground/wiki/Query-Planning-&amp;-Optimization-II.a:-VolcanoPlanner-Basics" target="_blank" rel="noopener">https://github.com/TatianaJin/calcite_playground/wiki/Query-Planning-&amp;-Optimization-II.a:-VolcanoPlanner-Basics</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;[toc]&lt;/p&gt;
&lt;p&gt;基于Flink1.4.2版本分析flink与calcite结合构建的flink sql模块。&lt;/p&gt;
    
    </summary>
    
      <category term="源码分析" scheme="http://www.aitozi.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
    
      <category term="Flink" scheme="http://www.aitozi.com/tags/Flink/"/>
    
      <category term="Calcite" scheme="http://www.aitozi.com/tags/Calcite/"/>
    
  </entry>
  
  <entry>
    <title>下篇·flink基于rocksdb的timerService</title>
    <link href="http://www.aitozi.com/flink-timerservice-based-on-rocksdb-2.html"/>
    <id>http://www.aitozi.com/flink-timerservice-based-on-rocksdb-2.html</id>
    <published>2019-03-16T04:13:03.000Z</published>
    <updated>2019-03-16T04:43:21.000Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p>
<p>接上文分析，要将timer改成基于rocksdb，其实就是要对存储timer的set和queue提供基于rocksdb的存储方案。以下我们基于flink1.7版本源码分析</p>
<a id="more"></a>
<p><strong>registerProcessingTimeTimer</strong></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><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">public void registerProcessingTimeTimer(N namespace, long time) &#123;</span><br><span class="line">	InternalTimer&lt;K, N&gt; oldHead = processingTimeTimersQueue.peek();</span><br><span class="line">	if (processingTimeTimersQueue.add(new TimerHeapInternalTimer&lt;&gt;(time, (K) keyContext.getCurrentKey(), namespace))) &#123;</span><br><span class="line">		long nextTriggerTime = oldHead != null ? oldHead.getTimestamp() : Long.MAX_VALUE;</span><br><span class="line">		// check if we need to re-schedule our timer to earlier</span><br><span class="line">		// 如果新加入的timer的时间更早触发，那么就需要把先前的timer取消</span><br><span class="line">		if (time &lt; nextTriggerTime) &#123;</span><br><span class="line">			if (nextTimer != null) &#123;</span><br><span class="line">				nextTimer.cancel(false);</span><br><span class="line">			&#125;</span><br><span class="line">			nextTimer = processingTimeService.registerTimer(time, this);</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>可以和看到1.4版本中的基本逻辑是一致的，只是存储方式变化了，下面我们就来分析一下新的存储方式是怎么实现的。在存储的选择上依然有Heap和RocksDB两个方式</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">switch (priorityQueueStateType) &#123;</span><br><span class="line">			case HEAP:</span><br><span class="line">				this.priorityQueueFactory = new HeapPriorityQueueSetFactory(keyGroupRange, numberOfKeyGroups, 128);</span><br><span class="line">				break;</span><br><span class="line">			case ROCKSDB:</span><br><span class="line">				this.priorityQueueFactory = new RocksDBPriorityQueueSetFactory();</span><br><span class="line">				break;</span><br><span class="line">			default:</span><br><span class="line">				throw new IllegalArgumentException(&quot;Unknown priority queue state type: &quot; + priorityQueueStateType);</span><br><span class="line">		&#125;</span><br></pre></td></tr></table></figure>
<p>从timerService的需求来看我们可以看到这样的几个需求：</p>
<ol>
<li>能够每次poll出最近需要触发的timer，实际上是需要维护一个小顶堆</li>
<li>能够对每一个key的timer去重</li>
</ol>
<p>针对这两个需求，总体来看基于Heap的实现是通过基于数组实现了一个二叉堆，具体实现类为<code>HeapPriorityQueue</code>, 然后针对去重的功能又继承该<code>PQ</code>，通过一个hashmap数组，数组的每一个元素代表一个KG的一组不重复timer，同时这组timer内部也维护了timer在二叉堆中存储的下标，方便<code>deleteTimer</code>时的快速删除。</p>
<h4 id="基于Heap的实现"><a href="#基于Heap的实现" class="headerlink" title="基于Heap的实现"></a>基于Heap的实现</h4><p><code>HeapPriorityQueueElement</code>,<code>AbstractHeapPriorityQueue</code>,<code>HeapPriorityQueue</code>,<code>HeapPriorityQueueSet</code></p>
<ol>
<li>存储基于数组，通过<code>HeapPriorityQueueElement</code>记录自己所在的index，可以达到快速删除的目的</li>
<li>数组中存储的是一个二叉树，数组的起始位置是从1开始，为了使一些热点方法做更少的计算</li>
</ol>
<p>在实现上是采用template的设计模式，主要实现逻辑交由子类来实现:</p>
<ul>
<li>addInternal</li>
<li>removeInternal</li>
<li>getHeadElementIndex</li>
</ul>
<h5 id="addInternal"><a href="#addInternal" class="headerlink" title="addInternal"></a>addInternal</h5><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></pre></td><td class="code"><pre><span class="line">public boolean add(@Nonnull T toAdd) &#123;</span><br><span class="line">	addInternal(toAdd);</span><br><span class="line">	return toAdd.getInternalIndex() == getHeadElementIndex();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>添加一个timer至数组中，返回值<code>false</code>表示队首的元素没有改变，<code>true</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></pre></td><td class="code"><pre><span class="line">protected void addInternal(@Nonnull T element) &#123;</span><br><span class="line">	final int newSize = increaseSizeByOne();</span><br><span class="line">	moveElementToIdx(element, newSize);</span><br><span class="line">	siftUp(newSize);</span><br><span class="line">&#125;</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><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">private int increaseSizeByOne() &#123;</span><br><span class="line">	final int oldArraySize = queue.length;</span><br><span class="line">	final int minRequiredNewSize = ++size;</span><br><span class="line">	if (minRequiredNewSize &gt;= oldArraySize) &#123;</span><br><span class="line">		final int grow = (oldArraySize &lt; 64) ? oldArraySize + 2 : oldArraySize &gt;&gt; 1;</span><br><span class="line">		// 当存储元素的个数大于数组长度时，需要进行扩容，通过`Arrays.copyOf`进行数组内容的拷贝</span><br><span class="line">		resizeQueueArray(oldArraySize + grow, minRequiredNewSize);</span><br><span class="line">	&#125;</span><br><span class="line">	// TODO implement shrinking as well?</span><br><span class="line">	return minRequiredNewSize;</span><br><span class="line">&#125;</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><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">// 将新加入的元素存储到相应的idx处，并且记录该元素在queue中的位置</span><br><span class="line">protected void moveElementToIdx(T element, int idx) &#123;</span><br><span class="line">		queue[idx] = element;</span><br><span class="line">		element.setInternalIndex(idx);</span><br><span class="line">	&#125;</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><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">private void siftUp(int idx) &#123;</span><br><span class="line">		final T[] heap = this.queue;</span><br><span class="line">		final T currentElement = heap[idx];</span><br><span class="line">		int parentIdx = idx &gt;&gt;&gt; 1;</span><br><span class="line">		</span><br><span class="line">		// 每次将比较的index，缩小一半，如果被比较元素的优先级高于新插入的元素就将被比较元素后移，直至比较到第一个元素。这样能够保证idx为1的元素是最早时间触发的</span><br><span class="line">		while (parentIdx &gt; 0 &amp;&amp; isElementPriorityLessThen(currentElement, heap[parentIdx])) &#123;</span><br><span class="line">			moveElementToIdx(heap[parentIdx], idx);</span><br><span class="line">			idx = parentIdx;</span><br><span class="line">			parentIdx &gt;&gt;&gt;= 1;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		moveElementToIdx(currentElement, idx);</span><br><span class="line">	&#125;</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><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 比较两个值的优先级</span><br><span class="line">private boolean isElementPriorityLessThen(T a, T b) &#123;</span><br><span class="line">		return elementPriorityComparator.comparePriority(a, b) &lt; 0;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>
<h5 id="removeInternal"><a href="#removeInternal" class="headerlink" title="removeInternal"></a>removeInternal</h5><ol>
<li>抽取第一个timer用以触发</li>
<li>用户删除某个timer的行为</li>
</ol>
<p>删除的方式是通过idx下标来实现快速删除的，这也就是<code>HeapPriorityQueueElement</code>中记录idx的作用</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><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">protected T removeInternal(int removeIdx) &#123;</span><br><span class="line">	T[] heap = this.queue;</span><br><span class="line">	T removedValue = heap[removeIdx];</span><br><span class="line"></span><br><span class="line">	// 要删除的idx应该和内部存储value值保存的idx一致</span><br><span class="line">	assert removedValue.getInternalIndex() == removeIdx;</span><br><span class="line"></span><br><span class="line">	final int oldSize = size;</span><br><span class="line"></span><br><span class="line">	// 删除的不是数组的最后一个元素需要进行位置的调整</span><br><span class="line">	if (removeIdx != oldSize) &#123;</span><br><span class="line">		T element = heap[oldSize];</span><br><span class="line">		// 将原先的最后一个元素放置到要删除的idx处，但是这样的放置没有考虑优先级</span><br><span class="line">		moveElementToIdx(element, removeIdx);</span><br><span class="line">		adjustElementAtIndex(element, removeIdx);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	heap[oldSize] = null;</span><br><span class="line"></span><br><span class="line">	--size;</span><br><span class="line">	return removedValue;</span><br><span class="line">&#125;</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><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">private void adjustElementAtIndex(T element, int index) &#123;</span><br><span class="line">	siftDown(index);</span><br><span class="line">	if (queue[index] == element) &#123;</span><br><span class="line">		siftUp(index);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</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><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">private void siftDown(int idx) &#123;</span><br><span class="line">	final T[] heap = this.queue;</span><br><span class="line">	final int heapSize = this.size;</span><br><span class="line"></span><br><span class="line">	final T currentElement = heap[idx];</span><br><span class="line">	int firstChildIdx = idx &lt;&lt; 1;</span><br><span class="line">	int secondChildIdx = firstChildIdx + 1;</span><br><span class="line"></span><br><span class="line">	if (isElementIndexValid(secondChildIdx, heapSize) &amp;&amp;</span><br><span class="line">		isElementPriorityLessThen(heap[secondChildIdx], heap[firstChildIdx])) &#123;</span><br><span class="line">		firstChildIdx = secondChildIdx;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	while (isElementIndexValid(firstChildIdx, heapSize) &amp;&amp;</span><br><span class="line">		isElementPriorityLessThen(heap[firstChildIdx], currentElement)) &#123;</span><br><span class="line">		moveElementToIdx(heap[firstChildIdx], idx);</span><br><span class="line">		idx = firstChildIdx;</span><br><span class="line">		firstChildIdx = idx &lt;&lt; 1;</span><br><span class="line">		secondChildIdx = firstChildIdx + 1;</span><br><span class="line"></span><br><span class="line">		if (isElementIndexValid(secondChildIdx, heapSize) &amp;&amp;</span><br><span class="line">			isElementPriorityLessThen(heap[secondChildIdx], heap[firstChildIdx])) &#123;</span><br><span class="line">			firstChildIdx = secondChildIdx;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	moveElementToIdx(currentElement, idx);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>以上的操作大致上是一个二叉堆的增删的调整过程，涉及的具体算法可以查阅下文末的资料。</p>
<hr>
<p>以下来分析rocksdb存储的实现</p>
<h4 id="KeyGroupPartitionedPriorityQueue"><a href="#KeyGroupPartitionedPriorityQueue" class="headerlink" title="KeyGroupPartitionedPriorityQueue"></a>KeyGroupPartitionedPriorityQueue</h4><p>基于rocksdb的存储是通过这个<code>KeyGroupPartitionedPriorityQueue</code>类来实现的，这个类中通过一个内存优先级队列，也就是上文中提到的内部实现的<code>HeapPriorityQueue</code>，用以存储所有KG的timer，而每一个分组的timer是如何存储呢？</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></pre></td><td class="code"><pre><span class="line">for (int i = 0; i &lt; keyGroupedHeaps.length; i++) &#123;</span><br><span class="line">			final PQ keyGroupSubHeap =</span><br><span class="line">				orderedCacheFactory.create(firstKeyGroup + i, totalKeyGroups, keyExtractor, elementPriorityComparator);</span><br><span class="line">			keyGroupedHeaps[i] = keyGroupSubHeap;</span><br><span class="line">			heapOfKeyGroupedHeaps.add(keyGroupSubHeap);</span><br><span class="line">		&#125;</span><br></pre></td></tr></table></figure>
<p>在这里的构造函数可以看到，其实是通过<code>orderedCacheFactory</code>,从字面意思看是一个有序的缓存，也就是为每一个KG创建一个有序的缓存类，并将其添加到优先级队列中，这里的<code>subHeap</code>也是一个可比较的类，相当于去取这两个<code>subHeap</code>的堆顶的元素拿出来比较下就可以知道这两个subheap的排序方式了。</p>
<p>比如<code>poll</code>的逻辑,首先先从HeapPQ中挑出堆顶（一个subPQ），然后再从这个PQ中取出堆顶就是要触发的timer了，而这个subPQ就是真是数据（timer）存储的地方了。</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></pre></td><td class="code"><pre><span class="line">public T poll() &#123;</span><br><span class="line">	final PQ headList = heapOfKeyGroupedHeaps.peek();</span><br><span class="line">	final T head = headList.poll();</span><br><span class="line">	heapOfKeyGroupedHeaps.adjustModifiedElement(headList);</span><br><span class="line">	return head;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="RocksDBCachingPriorityQueueSet"><a href="#RocksDBCachingPriorityQueueSet" class="headerlink" title="RocksDBCachingPriorityQueueSet"></a>RocksDBCachingPriorityQueueSet</h4><p>这个是上节中<code>subHeap</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><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">private void checkRefillCacheFromStore() &#123;</span><br><span class="line">		// 不是所有的元素都在cache(treeset)中，并且cache为空  </span><br><span class="line">		if (!allElementsInCache &amp;&amp; orderedCache.isEmpty()) &#123;</span><br><span class="line">			try (final RocksBytesIterator iterator = orderedBytesIterator()) &#123;</span><br><span class="line">				// 捞取rocksdb中这个columnFamily的部分数据填充treeset至maxsize</span><br><span class="line">				orderedCache.bulkLoadFromOrderedIterator(iterator);</span><br><span class="line">				allElementsInCache = !iterator.hasNext();</span><br><span class="line">			&#125; catch (Exception e) &#123;</span><br><span class="line">				throw new FlinkRuntimeException(&quot;Exception while refilling store from iterator.&quot;, e);</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>
<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><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">public E peek() &#123;</span><br><span class="line"></span><br><span class="line">		checkRefillCacheFromStore();</span><br><span class="line"></span><br><span class="line">		if (peekCache != null) &#123;</span><br><span class="line">			// 这个是维护的全局变量，只有在堆顶改变后在会置为null</span><br><span class="line">			return peekCache;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		byte[] firstBytes = orderedCache.peekFirst();</span><br><span class="line">		if (firstBytes != null) &#123;</span><br><span class="line">			peekCache = deserializeElement(firstBytes);</span><br><span class="line">			return peekCache;</span><br><span class="line">		&#125; else &#123;</span><br><span class="line">			return null;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</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><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">public E poll() &#123;</span><br><span class="line"></span><br><span class="line">		checkRefillCacheFromStore();</span><br><span class="line"></span><br><span class="line">		final byte[] firstBytes = orderedCache.pollFirst();</span><br><span class="line"></span><br><span class="line">		if (firstBytes == null) &#123;</span><br><span class="line">			return null;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		// write-through sync</span><br><span class="line">		// 为什么不需要删除treeset中的元素呢？</span><br><span class="line">		removeFromRocksDB(firstBytes);</span><br><span class="line"></span><br><span class="line">		// 删除了这个columnFamily最后一个元素</span><br><span class="line">		if (orderedCache.isEmpty()) &#123;</span><br><span class="line">			seekHint = firstBytes;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		// 少一步反序列化的操作</span><br><span class="line">		if (peekCache != null) &#123;</span><br><span class="line">			E fromCache = peekCache;</span><br><span class="line">			peekCache = null;</span><br><span class="line">			return fromCache;</span><br><span class="line">		&#125; else &#123;</span><br><span class="line">			return deserializeElement(firstBytes);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</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><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">public boolean add(@Nonnull E toAdd) &#123;</span><br><span class="line"></span><br><span class="line">		checkRefillCacheFromStore();</span><br><span class="line"></span><br><span class="line">		final byte[] toAddBytes = serializeElement(toAdd);</span><br><span class="line"></span><br><span class="line">		final boolean cacheFull = orderedCache.isFull();</span><br><span class="line"></span><br><span class="line">		// 如果cache没满并且之前所有元素都在cache中了  或者新加入的元素的优先级通过byte数组的优先级比较发现应该在堆顶</span><br><span class="line">		if ((!cacheFull &amp;&amp; allElementsInCache) ||</span><br><span class="line">			LEXICOGRAPHIC_BYTE_COMPARATOR.compare(toAddBytes, orderedCache.peekLast()) &lt; 0) &#123;</span><br><span class="line"></span><br><span class="line">			if (cacheFull) &#123;</span><br><span class="line">				// we drop the element with lowest priority from the cache</span><br><span class="line">				orderedCache.pollLast();</span><br><span class="line">				// the dropped element is now only in the store</span><br><span class="line">				allElementsInCache = false;</span><br><span class="line">			&#125;</span><br><span class="line"></span><br><span class="line">			// 用来判重</span><br><span class="line">			if (orderedCache.add(toAddBytes)) &#123;</span><br><span class="line">				// write-through sync</span><br><span class="line">				addToRocksDB(toAddBytes);</span><br><span class="line">				if (toAddBytes == orderedCache.peekFirst()) &#123;</span><br><span class="line">					// 说明新的写入导致了堆顶变化</span><br><span class="line">					peekCache = null;</span><br><span class="line">					return true;</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125; else &#123;</span><br><span class="line">			// 如果cache满了，或者不是所有的元素都在cache中，说明新来的数据一定不是堆顶的数据</span><br><span class="line">			// we only added to the store</span><br><span class="line">			addToRocksDB(toAddBytes);</span><br><span class="line">			allElementsInCache = false;</span><br><span class="line">		&#125;</span><br><span class="line">		return false;</span><br><span class="line">	&#125;</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><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">public boolean remove(@Nonnull E toRemove) &#123;</span><br><span class="line"></span><br><span class="line">		checkRefillCacheFromStore();</span><br><span class="line"></span><br><span class="line">		final byte[] oldHead = orderedCache.peekFirst();</span><br><span class="line"></span><br><span class="line">		if (oldHead == null) &#123;</span><br><span class="line">			return false;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		final byte[] toRemoveBytes = serializeElement(toRemove);</span><br><span class="line"></span><br><span class="line">		// write-through sync</span><br><span class="line">		removeFromRocksDB(toRemoveBytes);</span><br><span class="line">		orderedCache.remove(toRemoveBytes);</span><br><span class="line"></span><br><span class="line">		if (orderedCache.isEmpty()) &#123;</span><br><span class="line">			seekHint = toRemoveBytes;</span><br><span class="line">			peekCache = null;</span><br><span class="line">			return true;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		if (oldHead != orderedCache.peekFirst()) &#123;</span><br><span class="line">			peekCache = null;</span><br><span class="line">			return true;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		return false;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>
<p>Iterator中seekHint的作用：</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><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">private RocksBytesIterator(@Nonnull RocksIteratorWrapper iterator) &#123;</span><br><span class="line">			this.iterator = iterator;</span><br><span class="line">			try &#123;</span><br><span class="line">				// We use our knowledge about the lower bound to issue a seek that is as close to the first element in</span><br><span class="line">				// the key-group as possible, i.e. we generate the next possible key after seekHint by appending one</span><br><span class="line">				// zero-byte.</span><br><span class="line">				iterator.seek(Arrays.copyOf(seekHint, seekHint.length + 1));</span><br><span class="line">				currentElement = nextElementIfAvailable();</span><br><span class="line">			&#125; catch (Exception ex) &#123;</span><br><span class="line">				// ensure resource cleanup also in the face of (runtime) exceptions in the constructor.</span><br><span class="line">				iterator.close();</span><br><span class="line">				throw new FlinkRuntimeException(&quot;Could not initialize ordered iterator.&quot;, ex);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br></pre></td></tr></table></figure>
<h4 id="再说checkpoint"><a href="#再说checkpoint" class="headerlink" title="再说checkpoint"></a>再说checkpoint</h4><p>在doc中作者也提到之所以要做这个feature除了因为timer过多会导致OOM等问题，还有一个原因是因为timer的属性虽然和keyed state很类似，但是代码管理以及checkpoint的方式都是单独的一块逻辑，并且checkpoint的持久化过程还是同步的（因为是以raw keyedstate的方式去进行的），再修改之后，每次注册的timeservice都会注册到<code>kvstatInfo</code>中，将checkpoint的逻辑统一到statebackend中并且实现了异步化。</p>
<p><a href="https://blog.csdn.net/u010224394/article/details/8834969" target="_blank" rel="noopener">https://blog.csdn.net/u010224394/article/details/8834969</a></p>
<p><a href="https://github.com/apache/flink/pull/6159" target="_blank" rel="noopener">https://github.com/apache/flink/pull/6159</a></p>
<p><a href="https://docs.google.com/document/d/1XbhJRbig5c5Ftd77d0mKND1bePyTC26Pz04EvxdA7Jc/edit" target="_blank" rel="noopener">https://docs.google.com/document/d/1XbhJRbig5c5Ftd77d0mKND1bePyTC26Pz04EvxdA7Jc/edit</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;[toc]&lt;/p&gt;
&lt;p&gt;接上文分析，要将timer改成基于rocksdb，其实就是要对存储timer的set和queue提供基于rocksdb的存储方案。以下我们基于flink1.7版本源码分析&lt;/p&gt;
    
    </summary>
    
      <category term="源码分析" scheme="http://www.aitozi.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
    
      <category term="Flink" scheme="http://www.aitozi.com/tags/Flink/"/>
    
  </entry>
  
  <entry>
    <title>上篇·flink基于rocksdb的timerService</title>
    <link href="http://www.aitozi.com/flink-timerservice-based-on-rocksdb.html"/>
    <id>http://www.aitozi.com/flink-timerservice-based-on-rocksdb.html</id>
    <published>2019-03-16T04:08:03.000Z</published>
    <updated>2019-03-16T04:36:28.000Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p>
<p>本文主要介绍flink中<code>TimerService based on Rocksdb</code>实现以及和之前版本的一个比较。</p>
<a id="more"></a>
<p><strong>动机</strong></p>
<ol>
<li>timer和keyed state分开单独管理，keyed state是由<code>KeyedStateBackend</code>管理，而timer是由<code>InternalTimerServeice</code>管理</li>
<li><code>InternalTimerServeice</code>现有的实现是基于heap的<code>HeapInternalTimerService</code>，当timer数量较多时会有OOM的问题.如果能像keyed state一样基于<code>KeyedStateBackend</code>管理，就能在timer数量比较多的时候选用rocksdb作为backend来解决扩展性的问题</li>
<li>timer目前的checkpoint过程是通过raw keyed state的方式，在同步的过程中完成写出到外置存储，并且对于snapshot和restore timer都单独维护了一份代码。这块代码和其他的keyed state的实现有很多相同之处（分隔到keygroup来实现rescale，元数据的序列化和持久化..）</li>
</ol>
<p><strong>实现目标</strong></p>
<ol>
<li>Have an implementation of timer services that operates on RocksDB.</li>
<li>Support asynchronous snapshots for all timer state.</li>
<li>Support incremental snapshots for timer state in RocksDB.</li>
<li>Integrate timer state as another form of keyed state in keyed state backends in a way that leverages the existing snapshotting code to eliminate special casing code paths that do similar things. As as nice side effect, this would also free the raw keyed state for user state. </li>
</ol>
<p><strong>源码分析</strong></p>
<p>首先我们来看一下1.4版本中timerService是怎么实现的,timerService 实现了以下两个接口：</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">InternalTimerService</span><br><span class="line">    long currentProcessingTime();</span><br><span class="line">    long currentWatermark();</span><br><span class="line">    void registerProcessingTimeTimer(N namespace, long time);</span><br><span class="line">    void deleteProcessingTimeTimer(N namespace, long time);</span><br><span class="line">    void registerEventTimeTimer(N namespace, long time);</span><br><span class="line">    void deleteEventTimeTimer(N namespace, long time);</span><br><span class="line">提供的是当前时间获取和注册timer的方法</span><br><span class="line"></span><br><span class="line">ProcessingTimeCallback</span><br><span class="line">    void onProcessingTime(long timestamp) throws Exception;</span><br></pre></td></tr></table></figure>
<p>在<code>HeapInternalTimerService</code>中分别维护了processing time和event time的timer集合</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">private final Set&lt;InternalTimer&lt;K, N&gt;&gt;[] processingTimeTimersByKeyGroup;</span><br><span class="line">private final PriorityQueue&lt;InternalTimer&lt;K, N&gt;&gt; processingTimeTimersQueue;</span><br><span class="line"></span><br><span class="line">private final Set&lt;InternalTimer&lt;K, N&gt;&gt;[] eventTimeTimersByKeyGroup;</span><br><span class="line">private final PriorityQueue&lt;InternalTimer&lt;K, N&gt;&gt; eventTimeTimersQueue;</span><br></pre></td></tr></table></figure>
<p><code>InternalTimer</code>是一个<code>Comparable</code>, 按照timer触发的时间进行比较，timer是由<code>key</code>,<code>namespace</code>,<code>timestamp</code>唯一确定，其实也可以理解成如果同一个key，namespace下只可以有一个时间事件被触发。<code>regsterTimer</code>实际上就是在executor中注册一个任务</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">return timerService.schedule(</span><br><span class="line">                    new TriggerTask(status, task, checkpointLock, target, timestamp), delay, TimeUnit.MILLISECONDS);</span><br></pre></td></tr></table></figure>
<p>同时每个timerService中还维护了一个<code>ProcessingTimeService</code>用以处理和processing time相关的时间操作，是对<code>ScheduledThreadPoolExecutor</code>的包装，提供一些周期性执行和将来某时执行一次的操作.</p>
<p>我们在回头看<code>HeapInternalTimerService</code>的实现：</p>
<h4 id="registerProcessingTimeTimer"><a href="#registerProcessingTimeTimer" class="headerlink" title="registerProcessingTimeTimer"></a>registerProcessingTimeTimer</h4><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><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></pre></td><td class="code"><pre><span class="line">public void registerProcessingTimeTimer(N namespace, long time) &#123;</span><br><span class="line">    InternalTimer&lt;K, N&gt; timer = new InternalTimer&lt;&gt;(time, (K) keyContext.getCurrentKey(), namespace);</span><br><span class="line"></span><br><span class="line">    // make sure we only put one timer per key into the queue</span><br><span class="line">    // 在存储timer的时候会存储两份，一份是通过Set的形式将各个Keygroup的区分开，另一份是按照时间顺序排除存储在一个`PriorityQueue`中</span><br><span class="line">    Set&lt;InternalTimer&lt;K, N&gt;&gt; timerSet = getProcessingTimeTimerSetForTimer(timer);</span><br><span class="line">    if (timerSet.add(timer)) &#123;</span><br><span class="line"></span><br><span class="line">        InternalTimer&lt;K, N&gt; oldHead = processingTimeTimersQueue.peek();</span><br><span class="line">        long nextTriggerTime = oldHead != null ? oldHead.getTimestamp() : Long.MAX_VALUE;</span><br><span class="line"></span><br><span class="line">        processingTimeTimersQueue.add(timer);</span><br><span class="line"></span><br><span class="line">        // check if we need to re-schedule our timer to earlier</span><br><span class="line">        // 如果新注册的timer比最近要触发的timer时间早，那么就会终止最近要触发的timer（如果已经跑起来了就不中断了）</span><br><span class="line">        if (time &lt; nextTriggerTime) &#123;</span><br><span class="line">            if (nextTimer != null) &#123;</span><br><span class="line">                nextTimer.cancel(false);</span><br><span class="line">            &#125;</span><br><span class="line">            // 通过ScheduledThreadPoolExecutor注册一个task</span><br><span class="line">            nextTimer = processingTimeService.registerTimer(time, this);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 获取这个timer的key所属的已经注册的timer列表，从上面的注释我们可以看出是为了保证不注册重复timer</span><br><span class="line">private Set&lt;InternalTimer&lt;K, N&gt;&gt; getProcessingTimeTimerSetForTimer(InternalTimer&lt;K, N&gt; timer) &#123;</span><br><span class="line">    checkArgument(localKeyGroupRange != null, &quot;The operator has not been initialized.&quot;);</span><br><span class="line">    int keyGroupIdx = KeyGroupRangeAssignment.assignToKeyGroup(timer.getKey(), this.totalKeyGroups);</span><br><span class="line">    return getProcessingTimeTimerSetForKeyGroup(keyGroupIdx);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">private Set&lt;InternalTimer&lt;K, N&gt;&gt; getProcessingTimeTimerSetForKeyGroup(int keyGroupIdx) &#123;</span><br><span class="line">    int localIdx = getIndexForKeyGroup(keyGroupIdx);</span><br><span class="line">    Set&lt;InternalTimer&lt;K, N&gt;&gt; timers = processingTimeTimersByKeyGroup[localIdx];</span><br><span class="line">    // 如过这个set没有出现过，就构建一个新的set存放这个key的timer</span><br><span class="line">    if (timers == null) &#123;</span><br><span class="line">        timers = new HashSet&lt;&gt;();</span><br><span class="line">        processingTimeTimersByKeyGroup[localIdx] = timers;</span><br><span class="line">    &#125;</span><br><span class="line">    return timers;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">private int getIndexForKeyGroup(int keyGroupIdx) &#123;</span><br><span class="line">    checkArgument(localKeyGroupRange.contains(keyGroupIdx),</span><br><span class="line">        &quot;Key Group &quot; + keyGroupIdx + &quot; does not belong to the local range.&quot;);</span><br><span class="line">    return keyGroupIdx - this.localKeyGroupRangeStartIdx;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="registerEventTimeTimer"><a href="#registerEventTimeTimer" class="headerlink" title="registerEventTimeTimer"></a>registerEventTimeTimer</h4><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">public void registerEventTimeTimer(N namespace, long time) &#123;</span><br><span class="line">    InternalTimer&lt;K, N&gt; timer = new InternalTimer&lt;&gt;(time, (K) keyContext.getCurrentKey(), namespace);</span><br><span class="line">    Set&lt;InternalTimer&lt;K, N&gt;&gt; timerSet = getEventTimeTimerSetForTimer(timer);</span><br><span class="line">    if (timerSet.add(timer)) &#123;</span><br><span class="line">        eventTimeTimersQueue.add(timer);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到eventtimer注册就不需要校验是否有将要执行的任务，因为eventtimer的实现不依赖于schedulerExxecutor。</p>
<h4 id="onProcessingTime"><a href="#onProcessingTime" class="headerlink" title="onProcessingTime"></a>onProcessingTime</h4><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><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">// 这个方法是配合registerProcessingTimer，通过SystemProcessingTimeService来实现timer语义，在timer中注册的任务会回调这个onProcessing方法</span><br><span class="line">public void onProcessingTime(long time) throws Exception &#123;</span><br><span class="line">    // null out the timer in case the Triggerable calls registerProcessingTimeTimer()</span><br><span class="line">    // inside the callback.</span><br><span class="line">    // 如果不置为null，在执行triggerTarget.onProcessingTime(timer);里面执行了registerProcessingTimeTimer会调用本任务的cancel</span><br><span class="line">    nextTimer = null;</span><br><span class="line"></span><br><span class="line">    InternalTimer&lt;K, N&gt; timer;</span><br><span class="line"></span><br><span class="line">    // 将processingTimeTimersQueue中所有小于当前时间的任务都取出进行出发</span><br><span class="line">    while ((timer = processingTimeTimersQueue.peek()) != null &amp;&amp; timer.getTimestamp() &lt;= time) &#123;</span><br><span class="line"></span><br><span class="line">        // 删除set中存储的timer</span><br><span class="line">        Set&lt;InternalTimer&lt;K, N&gt;&gt; timerSet = getProcessingTimeTimerSetForTimer(timer);</span><br><span class="line"></span><br><span class="line">        timerSet.remove(timer);</span><br><span class="line">        processingTimeTimersQueue.remove();</span><br><span class="line"></span><br><span class="line">        // 每次触发之前需要设置当前的key</span><br><span class="line">        keyContext.setCurrentKey(timer.getKey());</span><br><span class="line">        triggerTarget.onProcessingTime(timer);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 说明队列中还存在还没到时间需要触发的timer，需要注册新的FutureTask</span><br><span class="line">    if (timer != null) &#123;</span><br><span class="line">        if (nextTimer == null) &#123;</span><br><span class="line">            nextTimer = processingTimeService.registerTimer(timer.getTimestamp(), this);</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>
<h4 id="advanceWatermark"><a href="#advanceWatermark" class="headerlink" title="advanceWatermark"></a>advanceWatermark</h4><p>processing timer是基于executor来实现的，eventtime 的timer触发就依赖于watermark来触发，每次收到上游的watermark会触发调用<code>advanceWatermark</code>来将eventtime queue中的timer取出进行触发</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></pre></td><td class="code"><pre><span class="line">public void processWatermark(Watermark mark) throws Exception &#123;</span><br><span class="line">    if (timeServiceManager != null) &#123;</span><br><span class="line">        timeServiceManager.advanceWatermark(mark);</span><br><span class="line">    &#125;</span><br><span class="line">    output.emitWatermark(mark);</span><br><span class="line">&#125;</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><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">// 这里的time就是最近的这次watermark的时间</span><br><span class="line">public void advanceWatermark(long time) throws Exception &#123;</span><br><span class="line">    currentWatermark = time;</span><br><span class="line"></span><br><span class="line">    InternalTimer&lt;K, N&gt; timer;</span><br><span class="line"></span><br><span class="line">    // 同样是取出所有的小于watermark的timer进行触发</span><br><span class="line">    while ((timer = eventTimeTimersQueue.peek()) != null &amp;&amp; timer.getTimestamp() &lt;= time) &#123;</span><br><span class="line"></span><br><span class="line">        Set&lt;InternalTimer&lt;K, N&gt;&gt; timerSet = getEventTimeTimerSetForTimer(timer);</span><br><span class="line">        timerSet.remove(timer);</span><br><span class="line">        eventTimeTimersQueue.remove();</span><br><span class="line"></span><br><span class="line">        keyContext.setCurrentKey(timer.getKey());</span><br><span class="line">        triggerTarget.onEventTime(timer);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="snapshot"><a href="#snapshot" class="headerlink" title="snapshot"></a>snapshot</h4><p>之前在分析state实现的时候也分析过，在对operator进行snapshot的时候有一步就是对timerservice的数据进行snapshot</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><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">KeyGroupsList allKeyGroups = out.getKeyGroupList();</span><br><span class="line">for (int keyGroupIdx : allKeyGroups) &#123;</span><br><span class="line">    out.startNewKeyGroup(keyGroupIdx);</span><br><span class="line"></span><br><span class="line">    timeServiceManager.snapshotStateForKeyGroup(</span><br><span class="line">        new DataOutputViewStreamWrapper(out), keyGroupIdx);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">public void snapshotStateForKeyGroup(DataOutputView stream, int keyGroupIdx) throws IOException &#123;</span><br><span class="line">    InternalTimerServiceSerializationProxy&lt;K, N&gt; serializationProxy =</span><br><span class="line">        new InternalTimerServiceSerializationProxy&lt;&gt;(timerServices, keyGroupIdx);</span><br><span class="line"></span><br><span class="line">    serializationProxy.write(stream);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>proxy这块主要是为兼容做了很多的工作</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><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">public void write(DataOutputView out) throws IOException &#123;</span><br><span class="line">    super.write(out);</span><br><span class="line"></span><br><span class="line">    out.writeInt(timerServices.size());</span><br><span class="line">    for (Map.Entry&lt;String, HeapInternalTimerService&lt;K, N&gt;&gt; entry : timerServices.entrySet()) &#123;</span><br><span class="line">        String serviceName = entry.getKey();</span><br><span class="line">        HeapInternalTimerService&lt;K, N&gt; timerService = entry.getValue();</span><br><span class="line"></span><br><span class="line">        out.writeUTF(serviceName);</span><br><span class="line">        InternalTimersSnapshotReaderWriters</span><br><span class="line">            .getWriterForVersion(VERSION, timerService.snapshotTimersForKeyGroup(keyGroupIdx))</span><br><span class="line">            .writeTimersSnapshot(out);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="restore"><a href="#restore" class="headerlink" title="restore"></a>restore</h4><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><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">protected void read(DataInputView in, boolean wasVersioned) throws IOException &#123;</span><br><span class="line">    int noOfTimerServices = in.readInt();</span><br><span class="line"></span><br><span class="line">    for (int i = 0; i &lt; noOfTimerServices; i++) &#123;</span><br><span class="line">        String serviceName = in.readUTF();</span><br><span class="line"></span><br><span class="line">        HeapInternalTimerService&lt;K, N&gt; timerService = timerServices.get(serviceName);</span><br><span class="line">        if (timerService == null) &#123;</span><br><span class="line">            timerService = new HeapInternalTimerService&lt;&gt;(</span><br><span class="line">                totalKeyGroups,</span><br><span class="line">                localKeyGroupRange,</span><br><span class="line">                keyContext,</span><br><span class="line">                processingTimeService);</span><br><span class="line">            timerServices.put(serviceName, timerService);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        int readerVersion = wasVersioned ? getReadVersion() : InternalTimersSnapshotReaderWriters.NO_VERSION;</span><br><span class="line">        InternalTimersSnapshot&lt;?, ?&gt; restoredTimersSnapshot = InternalTimersSnapshotReaderWriters</span><br><span class="line">            .getReaderForVersion(readerVersion, userCodeClassLoader)</span><br><span class="line">            .readTimersSnapshot(in);</span><br><span class="line"></span><br><span class="line">        timerService.restoreTimersForKeyGroup(restoredTimersSnapshot, keyGroupIdx);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<p>本文主要是对1.4版本的分析，下一篇文章基于1.7版本再分析<code>timerservice on rocksdb</code>的实现</p>
<p>参考: </p>
<p><a href="https://docs.google.com/document/d/1XbhJRbig5c5Ftd77d0mKND1bePyTC26Pz04EvxdA7Jc/edit#heading=h.17v0k3363r6q" target="_blank" rel="noopener">https://docs.google.com/document/d/1XbhJRbig5c5Ftd77d0mKND1bePyTC26Pz04EvxdA7Jc/edit#heading=h.17v0k3363r6q</a></p>
<p><a href="https://issues.apache.org/jira/browse/FLINK-9485" target="_blank" rel="noopener">https://issues.apache.org/jira/browse/FLINK-9485</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;[toc]&lt;/p&gt;
&lt;p&gt;本文主要介绍flink中&lt;code&gt;TimerService based on Rocksdb&lt;/code&gt;实现以及和之前版本的一个比较。&lt;/p&gt;
    
    </summary>
    
      <category term="源码分析" scheme="http://www.aitozi.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
    
      <category term="Flink" scheme="http://www.aitozi.com/tags/Flink/"/>
    
  </entry>
  
  <entry>
    <title>flink网络传输的前世今生</title>
    <link href="http://www.aitozi.com/flink-network-feature.html"/>
    <id>http://www.aitozi.com/flink-network-feature.html</id>
    <published>2019-03-16T03:57:03.000Z</published>
    <updated>2019-03-16T04:06:26.000Z</updated>
    
    <content type="html"><![CDATA[<p>flink的网络传输在1.5版本进行了重构，本文就这个feature来对flink网络传输进行系统的源码分析</p>
<a id="more"></a>
<h3 id="发送端"><a href="#发送端" class="headerlink" title="发送端"></a>发送端</h3><p>首先我们先来看数据发送端的主要流程如下：</p>
<p><strong>数据发送链路</strong></p>
<blockquote>
<p><code>RecordWriter =&gt; ResultPartition =&gt; ResultSubPartition =&gt; ResultSubpartitionView =&gt; BufferAvailabilityListener =&gt; PartitionRequestQueue</code><br>解释一下： 用户程序中调用<code>output.collect()</code>,首先会通过<code>RecordWriter</code>进行数据或者event的序列化。并且将其从堆内内存拷贝至堆外内存，然后添加至相应的<code>ResultPartition</code>中。<code>ResultPartition</code>根据数据<code>selectChannel</code>发送给下游的哪个<code>subIndex</code>,<code>BufferConsumer</code>就会被添加到相应的subpartition所维护的一个双端队列中。在某些条件下需要通过,在服务启动最开始注册上来的<code>ResultSubpartitionView</code>去通知消费端来进行消费buffer，view做的事情就是通过调用<code>BufferAvailableListener</code>的具体实现来进行通知事件通知。最终在netty端，通过<code>PartitionRequestQueue</code>进行最终的buffer发送。</p>
</blockquote>
<p>上面讲述了大体的流程，下面我们来结合代码来进行细节分析，下面代码可能会结合1.4和1.7两个版本来进行讲解</p>
<h4 id="RecordWriter"><a href="#RecordWriter" class="headerlink" title="RecordWriter"></a>RecordWriter</h4><h4 id="RecordSerializer"><a href="#RecordSerializer" class="headerlink" title="RecordSerializer"></a>RecordSerializer</h4><p>在1.4版本中序列化器是和下游的并发度一一绑定的，这样会导致一个问题，比如发送下游是hash的分区模式的话，在上游的每一个并发度就会存储5MB的序列化后的缓存数据，当下游的并发较大的时候就会占据比较大的内存，带来一定的gc问题。序列化器会负责做这样几件事情：</p>
<ul>
<li>数据的序列化<ul>
<li>在写数据的时候并不会校验缓存块的大小</li>
<li>写的时候同时用一个4字节的bytebuffer记录数据的大小，有多少个字节</li>
</ul>
</li>
<li>数据序列化结果的拷贝，对拷贝结果的判断<ul>
<li>数据拷贝了一部分，memorysegment已经满了</li>
<li>拷贝了完整记录</li>
<li>拷贝了完整记录，并且segment满了</li>
</ul>
</li>
<li>缓存清理</li>
<li>…</li>
</ul>
<p>重点拷贝过程</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><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">private boolean copyFromSerializerToTargetChannel(int targetChannel) throws IOException, InterruptedException &#123;</span><br><span class="line">	// We should reset the initial position of the intermediate serialization buffer before</span><br><span class="line">	// copying, so the serialization results can be copied to multiple target buffers.</span><br><span class="line">	// 这一步reset是为了在数据发送如果是broadcast这种一份数据需要发送多个下游通道的时候，就可以只序列化一次，后续数据发送的时候只需要将bytebuffer</span><br><span class="line">	// 的position值值置到0就可以了。</span><br><span class="line">	serializer.reset();</span><br><span class="line"></span><br><span class="line">	boolean pruneTriggered = false;</span><br><span class="line">	BufferBuilder bufferBuilder = getBufferBuilder(targetChannel);</span><br><span class="line">	SerializationResult result = serializer.copyToBufferBuilder(bufferBuilder);</span><br><span class="line">	// buffer没写满说明数据肯定已经写完了，直接进行下面的逻辑</span><br><span class="line">	while (result.isFullBuffer()) &#123;</span><br><span class="line">		// buffer写满了，首先将bufferBuilder标记为写完了，就是将positionMarker置为相反数</span><br><span class="line">		numBytesOut.inc(bufferBuilder.finish());</span><br><span class="line">		numBuffersOut.inc();</span><br><span class="line"></span><br><span class="line">		// If this was a full record, we are done. Not breaking out of the loop at this point</span><br><span class="line">		// will lead to another buffer request before breaking out (that would not be a</span><br><span class="line">		// problem per se, but it can lead to stalls in the pipeline).</span><br><span class="line">		// buffer写满，并且记录也写满了，那么发送到这个channel就完成了，否则就需要继续申请bufferBuilder继续拷贝</span><br><span class="line">		if (result.isFullRecord()) &#123;</span><br><span class="line">			pruneTriggered = true;</span><br><span class="line">			bufferBuilders[targetChannel] = Optional.empty();</span><br><span class="line">			break;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		bufferBuilder = requestNewBufferBuilder(targetChannel);</span><br><span class="line">		result = serializer.copyToBufferBuilder(bufferBuilder);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</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><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">@Override</span><br><span class="line">public SerializationResult copyToBufferBuilder(BufferBuilder targetBuffer) &#123;</span><br><span class="line">	targetBuffer.append(lengthBuffer);</span><br><span class="line">	targetBuffer.append(dataBuffer);</span><br><span class="line">	targetBuffer.commit();</span><br><span class="line"></span><br><span class="line">	return getSerializationResult(targetBuffer);</span><br><span class="line">&#125;</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><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">public int append(ByteBuffer source) &#123;</span><br><span class="line">	checkState(!isFinished());</span><br><span class="line"></span><br><span class="line">	int needed = source.remaining();</span><br><span class="line">	int available = getMaxCapacity() - positionMarker.getCached();</span><br><span class="line">	// segment不一定足够大，可能存不下这批buffer, 堆外内存拷贝的时候需要提前计算好可以拷贝的量，否则会有异常</span><br><span class="line">	int toCopy = Math.min(needed, available);</span><br><span class="line"></span><br><span class="line">	// 将source buffer中的数据/堆内存，put至memorySegment中，利用Unsafe进行数据拷贝</span><br><span class="line">	memorySegment.put(positionMarker.getCached(), source, toCopy);</span><br><span class="line">	// 设置新的position</span><br><span class="line">	positionMarker.move(toCopy);</span><br><span class="line">	return toCopy;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="BufferBuilder，BufferConsumer，PositionMarker"><a href="#BufferBuilder，BufferConsumer，PositionMarker" class="headerlink" title="BufferBuilder，BufferConsumer，PositionMarker"></a>BufferBuilder，BufferConsumer，PositionMarker</h4><p>在上面copy代码中看到其实拷贝的时候是依赖buffer的,如果没有申请到<code>BufferBuiler</code>,是会一直blocking的，那么这个bufferbuilder是什么呢？</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></pre></td><td class="code"><pre><span class="line">private BufferBuilder requestNewBufferBuilder(int targetChannel) throws IOException, InterruptedException &#123;</span><br><span class="line">	checkState(!bufferBuilders[targetChannel].isPresent() || bufferBuilders[targetChannel].get().isFinished());</span><br><span class="line"></span><br><span class="line">	BufferBuilder bufferBuilder = targetPartition.getBufferProvider().requestBufferBuilderBlocking();</span><br><span class="line">	bufferBuilders[targetChannel] = Optional.of(bufferBuilder);</span><br><span class="line">	// 一个bufferbuilder对应一个bufferconsumer</span><br><span class="line">	targetPartition.addBufferConsumer(bufferBuilder.createBufferConsumer(), targetChannel);</span><br><span class="line">	return bufferBuilder;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在向<code>BufferProvider</code>，一般是localBufferPool申请完得到一个memorysegment后，将其封装成一个bufferbuilder，每一个bufferbuilder会对应<br>一个bufferconsumer和positionMarker，positionMarker会标记生产端的数据写到多少个字节了，这个在消费端的时候也会用到这个position，<br>由于是多线程使用所以position的值需要被标记成<code>volatile</code>来保证数据的可见性，每次消费端拉取数据的时候，对于没有写完的buffer同样可以进行消费，<br>消费前更新一个buffer的position真实位置，这里用到了一个小技巧，由于数据在生产的时候需要频繁的更新position，如果是<code>volatile</code>的，<br>虽然比较轻量，频繁更新也是比较大的开销，因此加入了一个<code>cachedPosition</code>，在写数据的时候只需要更新builder中的<code>cachedPosition</code>，生产端每次<br>完成一批的书写才会commit给<code>volatile position</code>，以此来减少缓存刷新。</p>
<p>从一个正在写的bufferbuiler中构建一个可消费的slice</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">public Buffer build() &#123;</span><br><span class="line">	// 获取最近builder，commit到的position</span><br><span class="line">	writerPosition.update();</span><br><span class="line">	int cachedWriterPosition = writerPosition.getCached();</span><br><span class="line">	// slice 切分只读区块</span><br><span class="line">	Buffer slice = buffer.readOnlySlice(currentReaderPosition, cachedWriterPosition - currentReaderPosition);</span><br><span class="line">	currentReaderPosition = cachedWriterPosition;</span><br><span class="line">	// 增加引用计数</span><br><span class="line">	return slice.retainBuffer();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="PartitionRequestQueue"><a href="#PartitionRequestQueue" class="headerlink" title="PartitionRequestQueue"></a>PartitionRequestQueue</h4><p>在将bufferConsumer添加到subpartition的队列之后，同时会在partitionRequestQueue中维护一个availableReader的队列，这个队列表示可以往下<br>下游发送的buffer数据，这样通过一个<code>while true</code>循环持续的将队列中的数据往下游发送，当然这个<code>availableReader</code>队列的维护既考量了上游subpartition<br>有没有buffer的因素，也考量了下游要发送的receiver端的credit的情况，如果没有credit也是无法进入这个待发送队列的。</p>
<h3 id="消费端"><a href="#消费端" class="headerlink" title="消费端"></a>消费端</h3><p><strong>数据接收链路</strong></p>
<blockquote>
<p><code>CreditBasedPartitionRequestClientHandler =&gt; RemoteInputChannel =&gt; SingleInputGate =&gt; BarrierHandler =&gt; StreamInputProcessor =&gt; StreamOperator</code><br>首先会通过netty client进行数据的接收，然后从localbufferpool申请内存接收数据，然后根据backlog的信息去决定是不是要给上游分发credit，以及数据处理的流程</p>
</blockquote>
<p>这里主要分析下credit的判断逻辑</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><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></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Receives the backlog from the producer&apos;s buffer response. If the number of available</span><br><span class="line"> * buffers is less than backlog + initialCredit, it will request floating buffers from the buffer</span><br><span class="line"> * pool, and then notify unannounced credits to the producer.</span><br><span class="line"> *</span><br><span class="line"> * @param backlog The number of unsent buffers in the producer&apos;s sub partition.</span><br><span class="line"> */</span><br><span class="line">void onSenderBacklog(int backlog) throws IOException &#123;</span><br><span class="line">	int numRequestedBuffers = 0;</span><br><span class="line"></span><br><span class="line">	synchronized (bufferQueue) &#123;</span><br><span class="line">		// Similar to notifyBufferAvailable(), make sure that we never add a buffer</span><br><span class="line">		// after releaseAllResources() released all buffers (see above for details).</span><br><span class="line">		if (isReleased.get()) &#123;</span><br><span class="line">			return;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		numRequiredBuffers = backlog + initialCredit;</span><br><span class="line">		// 检查当前input通道的buffer是否做够上游produce所需要的buffer，如果不够就去bufferpool申请</span><br><span class="line">		while (bufferQueue.getAvailableBufferSize() &lt; numRequiredBuffers &amp;&amp; !isWaitingForFloatingBuffers) &#123;</span><br><span class="line">			Buffer buffer = inputGate.getBufferPool().requestBuffer();</span><br><span class="line">			if (buffer != null) &#123;</span><br><span class="line">				// 申请到buffer之后先占据住</span><br><span class="line">				bufferQueue.addFloatingBuffer(buffer);</span><br><span class="line">				numRequestedBuffers++;</span><br><span class="line">				//  没有足够的buffer，那么注册回调等buffer回收</span><br><span class="line">			&#125; else if (inputGate.getBufferProvider().addBufferListener(this)) &#123;</span><br><span class="line">				// If the channel has not got enough buffers, register it as listener to wait for more floating buffers.</span><br><span class="line">				isWaitingForFloatingBuffers = true;</span><br><span class="line">				break;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">	// 如果生产端有buffer需求，并且之前的unannouncedCredit为0那么就需要通知上游有buffer了</span><br><span class="line">	if (numRequestedBuffers &gt; 0 &amp;&amp; unannouncedCredit.getAndAdd(numRequestedBuffers) == 0) &#123;</span><br><span class="line">		notifyCreditAvailable();</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="整理流程图"><a href="#整理流程图" class="headerlink" title="整理流程图"></a>整理流程图</h3><p><img src="https://raw.githubusercontent.com/Aitozi/images/master/flink/flink%E7%BD%91%E7%BB%9C%E6%A0%88%E5%9B%BE%E8%A7%A3.png" alt="flink-network" title="flink-network"></p>
<h3 id="netty内存的优化"><a href="#netty内存的优化" class="headerlink" title="netty内存的优化"></a>netty内存的优化</h3><p>以下是message encode的时候的一段代码</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><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">// only allocate header buffer - we will combine it with the data buffer below</span><br><span class="line">headerBuf = allocateBuffer(allocator, ID, messageHeaderLength, buffer.readableBytes(), false);</span><br><span class="line"></span><br><span class="line">receiverId.writeTo(headerBuf);</span><br><span class="line">headerBuf.writeInt(sequenceNumber);</span><br><span class="line">headerBuf.writeInt(backlog);</span><br><span class="line">headerBuf.writeBoolean(isBuffer);</span><br><span class="line">headerBuf.writeInt(buffer.readableBytes());</span><br><span class="line"></span><br><span class="line">CompositeByteBuf composityBuf = allocator.compositeDirectBuffer();</span><br><span class="line">composityBuf.addComponent(headerBuf);</span><br><span class="line">composityBuf.addComponent(buffer);</span><br><span class="line">// update writer index since we have data written to the components:</span><br><span class="line">composityBuf.writerIndex(headerBuf.writerIndex() + buffer.writerIndex());</span><br><span class="line">return composityBuf;</span><br></pre></td></tr></table></figure>
<p>可以看到这里和以前版本不一样的地方就是不需要再去申请一块netty内存做一次拷贝，因为这里将buffer对象的实现直接改成了继承netty的ByteBuf类，<br>所以减少了一次netty申请directBuffer以及从堆外拷贝到netty directBuffer的开销。在buffer处理完由netty回收时会放回<code>localBufferPool</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></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">protected void deallocate() &#123;</span><br><span class="line">	recycler.recycle(memorySegment); // 在网络传输完内存释放的时候直接将segment回收到localbufferpool中</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="和flink1-4相比有了哪些改进"><a href="#和flink1-4相比有了哪些改进" class="headerlink" title="和flink1.4相比有了哪些改进"></a>和flink1.4相比有了哪些改进</h3><p><a href="https://docs.google.com/document/d/1chTOuOqe0sBsjldA_r-wXYeSIhU2zRGpUaTaik7QZ84" target="_blank" rel="noopener">https://docs.google.com/document/d/1chTOuOqe0sBsjldA_r-wXYeSIhU2zRGpUaTaik7QZ84</a></p>
<p><a href="https://issues.apache.org/jira/browse/FLINK-7282?subTaskView=all" target="_blank" rel="noopener">https://issues.apache.org/jira/browse/FLINK-7282?subTaskView=all</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;flink的网络传输在1.5版本进行了重构，本文就这个feature来对flink网络传输进行系统的源码分析&lt;/p&gt;
    
    </summary>
    
      <category term="源码分析" scheme="http://www.aitozi.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
    
      <category term="Flink" scheme="http://www.aitozi.com/tags/Flink/"/>
    
      <category term="Netty" scheme="http://www.aitozi.com/tags/Netty/"/>
    
  </entry>
  
  <entry>
    <title>git常用命令大全</title>
    <link href="http://www.aitozi.com/git-advance-tips-keeping-on-updating.html"/>
    <id>http://www.aitozi.com/git-advance-tips-keeping-on-updating.html</id>
    <published>2019-03-16T03:40:03.000Z</published>
    <updated>2019-03-16T03:52:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>主要是工作中常用的一些git命令和一些场景的使用方式</p>
<a id="more"></a>
<h2 id="常见命令"><a href="#常见命令" class="headerlink" title="常见命令"></a>常见命令</h2><h3 id="checkout"><a href="#checkout" class="headerlink" title="checkout"></a>checkout</h3><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">git checkout -b release-1.7.2 origin/release-1.7.2  # 从远端仓库checkout出release-1.7.2分支</span><br><span class="line">git checkout -- filename # 回退某文件至修改前的状态，也可用于误删文件恢复</span><br><span class="line">git checkout 0c6ded6af7068ff9fa4505d81855a38fc9861871 filename # 将某文件回退至某个版本</span><br></pre></td></tr></table></figure>
<h3 id="commit"><a href="#commit" class="headerlink" title="commit"></a>commit</h3><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">git commit --amend   # 修改commit信息</span><br></pre></td></tr></table></figure>
<h3 id="stash"><a href="#stash" class="headerlink" title="stash"></a>stash</h3><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></pre></td><td class="code"><pre><span class="line">git stash # 保存工作现场  没有commit的内容</span><br><span class="line">git stash list # 查看stash队列</span><br><span class="line">git stash apply stash@&#123;num&#125;  # 恢复对应的stash</span><br><span class="line">git stash pop # 应用并删除最上面的stash</span><br></pre></td></tr></table></figure>
<h3 id="push"><a href="#push" class="headerlink" title="push"></a>push</h3><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">git push &lt;远程主机名&gt; &lt;本地分支名&gt;:&lt;远程分支名&gt;</span><br><span class="line">git push origin yarn_hdfs:yarn_and_hdfs_tools</span><br></pre></td></tr></table></figure>
<h3 id="tag"><a href="#tag" class="headerlink" title="tag"></a>tag</h3><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></pre></td><td class="code"><pre><span class="line">git ls-remote --tags upstream # 查看远端的tag列表</span><br><span class="line">git fetch --all --tags --prune # 获取远程所有的tag，如果有origin和upstream两个，那么都会拉下来 https://stackoverflow.com/questions/35979642/what-is-git-tag-how-to-create-tags-how-to-checkout-git-remote-tags  有时远端仓库更新了tag就需要拉一次</span><br><span class="line">git tag --list # 列出所有的tag</span><br><span class="line">git checkout -b tset v0.1.0  # checkout到某tag</span><br></pre></td></tr></table></figure>
<h3 id="rebase"><a href="#rebase" class="headerlink" title="rebase"></a>rebase</h3><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">git rebase -i commid # [当前commit,指定commitId) 左开右闭 # 修改commit信息，合并commit, 调整commit顺序，将一个类型的commit合并在一起</span><br><span class="line">git rebase -i HEAD~2</span><br></pre></td></tr></table></figure>
<h3 id="cherry-pick"><a href="#cherry-pick" class="headerlink" title="cherry-pick"></a>cherry-pick</h3><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">git cherry-pick A..B # 合并单个和多个</span><br><span class="line">git cherry-pick A</span><br></pre></td></tr></table></figure>
<h3 id="reset"><a href="#reset" class="headerlink" title="reset"></a>reset</h3><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">git reset --hard &lt;sha1-commit-id&gt; # 直接删除到这个commitId</span><br><span class="line">git reset --soft</span><br></pre></td></tr></table></figure>
<h3 id="branch"><a href="#branch" class="headerlink" title="branch"></a>branch</h3><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">git branch -m &lt;old_name&gt; &lt;new_name&gt; # 重命名 branch 名称</span><br><span class="line">git branch -m &lt;new_name&gt; # 重命名 branch 名称</span><br></pre></td></tr></table></figure>
<h3 id="log"><a href="#log" class="headerlink" title="log"></a>log</h3><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">git log --graph --oneline --decorate # 查看提交历史,https://segmentfault.com/a/1190000008039809</span><br><span class="line">git log --merges # merge 历史</span><br></pre></td></tr></table></figure>
<h2 id="常见操作方式"><a href="#常见操作方式" class="headerlink" title="常见操作方式"></a>常见操作方式</h2><h3 id="为仓库添加一个源"><a href="#为仓库添加一个源" class="headerlink" title="为仓库添加一个源"></a>为仓库添加一个源</h3><p>例如在内部flink仓库添加一个社区的源用以合并代码</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">git remote add upstream https://github.com/apache/flink.git</span><br><span class="line">git pull upstream master # 指定上游和分支拉取代码</span><br></pre></td></tr></table></figure>
<h3 id="设置新的分支与远程分支的对应关系"><a href="#设置新的分支与远程分支的对应关系" class="headerlink" title="设置新的分支与远程分支的对应关系"></a>设置新的分支与远程分支的对应关系</h3><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">git branch --set-upstream-to=origin/dev_1.3.2_minwenjun</span><br><span class="line">git branch --set-upstream release-1.2.0-100 origin/release-1.2.0-100</span><br></pre></td></tr></table></figure>
<h3 id="克隆单个分支的代码"><a href="#克隆单个分支的代码" class="headerlink" title="克隆单个分支的代码"></a>克隆单个分支的代码</h3><p>常用于review大的MR，单独拉取用户提交的一个分支</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">git clone [url] -b [branch-name] --single-branch</span><br><span class="line">git clone https://github.com/sihuazhou/flink.git -b FLINK-9804 --single-branch</span><br></pre></td></tr></table></figure>
<h3 id="cherry-pick-merge-commit"><a href="#cherry-pick-merge-commit" class="headerlink" title="cherry-pick merge commit"></a>cherry-pick merge commit</h3><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">https://stackoverflow.com/questions/9229301/git-cherry-pick-says-38c74d-is-a-merge-but-no-m-option-was-given</span><br><span class="line">git cherry-pick -m 1 fd9f578</span><br><span class="line">git show --pretty=raw fd48e1ab722c20c196adb3e68583ba0d046b9cad (merge commit)</span><br></pre></td></tr></table></figure>
<h3 id="将当前代码提交到另一个仓库"><a href="#将当前代码提交到另一个仓库" class="headerlink" title="将当前代码提交到另一个仓库"></a>将当前代码提交到另一个仓库</h3><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">git remote add origin_repo_b git@server_ip:/path/repo_b.git</span><br><span class="line">git push origin_repo_b branch_a(要推的那个本地分支的名字)</span><br></pre></td></tr></table></figure>
<h3 id="git修改传输协议"><a href="#git修改传输协议" class="headerlink" title="git修改传输协议"></a>git修改传输协议</h3><p>修改你本地的ssh remote url. 不用https协议，改用git协议</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">git remote -v</span><br><span class="line">git remote set-url origin</span><br><span class="line"></span><br><span class="line">[minwenjun@bigdata-test04 flink-metric-analyse]$ git remote set-url origin git@github.com:minwenjun/flink-metric-analyse.git</span><br><span class="line">[minwenjun@bigdata-test04 flink-metric-analyse]$ git remote -v</span><br><span class="line">origin	git@ github.com:minwenjun/flink-metric-analyse.git (fetch)</span><br><span class="line">origin	git@github.com:minwenjun/flink-metric-analyse.git (push)</span><br></pre></td></tr></table></figure>
<p>参考资料:</p>
<p><a href="https://yuzhouwan.com/posts/30041/" target="_blank" rel="noopener">https://yuzhouwan.com/posts/30041/</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;主要是工作中常用的一些git命令和一些场景的使用方式&lt;/p&gt;
    
    </summary>
    
      <category term="编程工具" scheme="http://www.aitozi.com/categories/%E7%BC%96%E7%A8%8B%E5%B7%A5%E5%85%B7/"/>
    
    
      <category term="Git" scheme="http://www.aitozi.com/tags/Git/"/>
    
  </entry>
  
  <entry>
    <title>maven java.lang.NoClassDefFoundError with provided scope</title>
    <link href="http://www.aitozi.com/maven-noclassdeffounderror.html"/>
    <id>http://www.aitozi.com/maven-noclassdeffounderror.html</id>
    <published>2019-03-15T15:12:03.000Z</published>
    <updated>2019-03-16T03:53:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>关于maven中执行类遇到的<code>java.lang.NoClassDefFoundError</code>的问题</p>
<a id="more"></a>
<p>昨天有个同事问我，在Flink某个包中加了一个类用来进行测试，结果运行就会报如下错误</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">Exception in thread &quot;main&quot; java.lang.NoClassDefFoundError: org/apache/flink/api/common/serialization/DeserializationSchema</span><br><span class="line">	at com.didi.flink.app.FlinkTableSinkTest.main(FlinkTableSinkTest.scala)</span><br><span class="line">Caused by: java.lang.ClassNotFoundException: org.apache.flink.api.common.serialization.DeserializationSchema</span><br><span class="line">	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)</span><br><span class="line">	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)</span><br><span class="line">	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)</span><br><span class="line">	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)</span><br><span class="line">	... 1 more</span><br></pre></td></tr></table></figure>
<p>排查后怀疑是pom中该jar的依赖scope是provided导致的,测试删除后就解决了。</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></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">	&lt;groupId&gt;org.apache.flink&lt;/groupId&gt;</span><br><span class="line">	&lt;artifactId&gt;flink-streaming-java_$&#123;scala.binary.version&#125;&lt;/artifactId&gt;</span><br><span class="line">	&lt;version&gt;$&#123;project.version&#125;&lt;/version&gt;</span><br><span class="line">	&lt;scope&gt;provided&lt;/scope&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>
<p>但是觉得这样会很麻烦，所有其他用到的类都需要去除provided了？顺手Google了一下，Stack Overflow上有人问过同样的问题（这个人竟然是之前blink meetup上的flink+zeppelin的演讲者<em>章剑锋（简锋）</em>）</p>
<p><a href="https://stackoverflow.com/questions/30453269/maven-provided-dependency-will-cause-noclassdeffounderror-in-intellij" target="_blank" rel="noopener">https://stackoverflow.com/questions/30453269/maven-provided-dependency-will-cause-noclassdeffounderror-in-intellij</a></p>
<p>简单的说是因为provided的scope只在编译期和test期间有效，所以正确的姿势应该是测试类就放在测试包下面测试，这样provided的包依然是有效的</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;关于maven中执行类遇到的&lt;code&gt;java.lang.NoClassDefFoundError&lt;/code&gt;的问题&lt;/p&gt;
    
    </summary>
    
      <category term="问题排查" scheme="http://www.aitozi.com/categories/%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/"/>
    
    
      <category term="Maven" scheme="http://www.aitozi.com/tags/Maven/"/>
    
  </entry>
  
  <entry>
    <title>to be or not to be in 2019</title>
    <link href="http://www.aitozi.com/2019-flag.html"/>
    <id>http://www.aitozi.com/2019-flag.html</id>
    <published>2019-03-14T17:25:03.000Z</published>
    <updated>2019-03-14T17:25:37.000Z</updated>
    
    <content type="html"><![CDATA[<p>2019年的flag</p>
<a id="more"></a>
<p>2019年的Q1快接近尾声了，是时候给今年来一个flag了，今年有以下3个目标：</p>
<ol>
<li>跑步500公里</li>
<li>读书10本+ <em>技术书籍不低于3本</em></li>
<li>flink，netty，hbase系列的源码分析博客及仓库更新</li>
<li>机器学习简单入门</li>
<li>简单学会尤克里里的弹奏</li>
<li>努力工作，攒钱</li>
</ol>
<p>截止时间:</p>
<p>———————————–2020.01.01———————————————–</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;2019年的flag&lt;/p&gt;
    
    </summary>
    
      <category term="杂七杂八" scheme="http://www.aitozi.com/categories/%E6%9D%82%E4%B8%83%E6%9D%82%E5%85%AB/"/>
    
    
      <category term="flag" scheme="http://www.aitozi.com/tags/flag/"/>
    
  </entry>
  
  <entry>
    <title>String,StringBuffer,StringBuilder的区别</title>
    <link href="http://www.aitozi.com/string-stringbuilder-stringbuffer.html"/>
    <id>http://www.aitozi.com/string-stringbuilder-stringbuffer.html</id>
    <published>2018-08-27T16:13:48.000Z</published>
    <updated>2019-03-14T17:09:57.000Z</updated>
    
    <content type="html"><![CDATA[<p>常用的jdk的组件的源码分析之：<code>String</code>,<code>StringBuffer</code>,<code>StringBuilder</code></p>
<a id="more"></a>
<p>String 字符串常量<br>StringBuffer 字符串变量（线程安全）<br>StringBuilder 字符串变量（非线程安全）</p>
<p>String和StringBuffer的主要性能区别其实在于 String是不可变的对象, 因此在每次对String类型进行改变的时候其实都等同于生成了一个新的 String对象，然后将指针指向新的String对象，所以经常改变内容的字符串最好不要用String，因为每次生成对象都会对系统性能产生影响，特别当内存中无引用对象多了以后，JVM的GC就会开始工作，那速度是一定会相当慢的。</p>
<p>那么String为什么要是不可变的呢？</p>
<h3 id="String类不可变的好处"><a href="#String类不可变的好处" class="headerlink" title="String类不可变的好处"></a>String类不可变的好处</h3><ol>
<li>只有当字符串是不可变的，字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间，因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的，那么String interning将不能实现，String interning是指对不同的字符串仅仅只保存一个，即不会保存多个相同的字符串。因为这样的话，如果变量改变了它的值，那么其它指向这个值的变量的值也会一起改变。</li>
<li>如果字符串是可变的，那么会引起很严重的安全问题。譬如，数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接，或者在socket编程中，主机名和端口都是以字符串的形式传入。因为字符串是不可变的，所以它的值是不可改变的，否则黑客们可以钻到空子，改变字符串指向的对象的值，造成安全漏洞。</li>
<li>因为字符串是不可变的，所以是多线程安全的，同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。</li>
<li>类加载器要用到字符串，不可变性提供了安全性，以便正确的类被加载。譬如你想加载java.sql.Connection类，而这个值被改成了myhacked.Connection，那么会对你的数据库造成不可知的破坏。</li>
<li>因为字符串是不可变的，所以在它创建的时候hashcode就被缓存了，不需要重新计算。这就使得字符串很适合作为Map中的键，字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。也同时指出一个理念，千万不要把可变类型作为HashMap和HashSet的键值</li>
</ol>
<h3 id="在java中如何设计不可变"><a href="#在java中如何设计不可变" class="headerlink" title="在java中如何设计不可变"></a>在java中如何设计不可变</h3><ol>
<li>对于属性不提供设值的方法</li>
<li>所有的属性定义为private final</li>
<li>类声明为final不允许继承</li>
<li>return deep cloned objects with copied content for all mutable fields in class</li>
</ol>
<p>翻看string的源码，可以看到string的本质是个char数组，并且使用final关键字修饰。但是char数组用final修饰只能让数组的引用地址不变，array数组还是可变的，主要是SUN的工程师没有暴露内部成员字段，所以String不可变主要在底层实现，而不是在final。</p>
<h3 id="String的内存存储"><a href="#String的内存存储" class="headerlink" title="String的内存存储"></a>String的内存存储</h3><p>一般而言，Java 对象在虚拟机的结构如下：</p>
<ul>
<li>对象头（object header）：8 个字节</li>
<li>Java 原始类型数据：如 int, float, char 等类型的数据，各类型数据占内存。<ul>
<li>boolean 1</li>
<li>byte</li>
<li>char 2</li>
<li>short </li>
<li>int 4 </li>
<li>long 8     </li>
</ul>
</li>
<li>引用（reference）：4 个字节</li>
<li>填充符（padding）</li>
</ul>
<p>然而，一个 Java 对象实际还会占用些额外的空间，如：对象的 class 信息、ID、在虚拟机中的状态。在 Oracle JDK 的 Hotspot 虚拟机中，一个普通的对象需要额外 8 个字节。</p>
<p>String对象的声明</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></pre></td><td class="code"><pre><span class="line">private final char value[]; </span><br><span class="line">private final int offset; </span><br><span class="line">private final int count; </span><br><span class="line">private int hash;</span><br></pre></td></tr></table></figure>
<p>那么因该如何计算该 String 所占的空间？</p>
<p>首先计算一个空的 char 数组所占空间，在 Java 里数组也是对象，因而数组也有对象头，故一个数组所占的空间为对象头所占的空间加上数组长度，即 8 + 4 = 12 字节 , 经过填充后为 16 字节。</p>
<p>那么一个空 String 所占空间为：</p>
<p>对象头（8 字节）+ char 数组（16 字节）+ 3 个 int（3 × 4 = 12 字节）+1 个 char 数组的引用 (4 字节 ) = 40 字节。</p>
<p>因此一个实际的 String 所占空间的计算公式如下：</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">8*( ( 8+2*n+4+12)+7 ) / 8 = 8*(int) ( ( ( (n) *2 )+43) /8 )</span><br></pre></td></tr></table></figure>
<p>在java中对String对象特殊对待，所以在heap上分为两块，一块是String constant pool存储java字符串常量，另一块存储普通对象和字符串对象，主要区别：</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">String a = &quot;abc&quot;;</span><br><span class="line">String b = new String(&quot;acb&quot;)</span><br></pre></td></tr></table></figure>
<p>第一种jvm会先去查找constant pool是否存在此常量，不存在就在constant pool上进行创建，第二种是在堆上创建对象，并且不会加入到constant pool上，因此可能会带来字符串重复占用内存的问题。可以调用String.intern()加入到String constant pool中，其实是JVM heap 中 PermGen 相应的区域。</p>
<p>jdk1.6和1.7还有所不同，jdk1.7的常量池是在堆中的</p>
<h3 id="StringBuffer"><a href="#StringBuffer" class="headerlink" title="StringBuffer"></a>StringBuffer</h3><p>StringBuffer和String不同，每次修改都会对 StringBuffer 对象本身进行操作，而不是生成新的对象，再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ，特别是字符串对象经常改变的情况下。而在某些特别情况下， String 对象的字符串拼接其实是被JVM解释成了 StringBuffer 对象的拼接，所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢，而特别是以下的字符串对象生成中， String效率是远要比 StringBuffer 快的：</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">String S1 = “This is only a” + “ simple” + “ test”;</span><br><span class="line">StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);</span><br></pre></td></tr></table></figure>
<p> 你会很惊讶的发现，生成 String S1 对象的速度简直太快了，而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏，在 JVM 眼里，这个</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></pre></td><td class="code"><pre><span class="line">String S1 = “This is only a” + “ simple” + “test”;</span><br><span class="line">其实就是：</span><br><span class="line">String S1 = “This is only a simple test”;</span><br></pre></td></tr></table></figure>
<p>当然不需要太多的时间了。但大家这里要注意的是，如果你的字符串是来自另外的 String 对象的话，速度就没那么快了，譬如：</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></pre></td><td class="code"><pre><span class="line">String S2 = “This is only a”;</span><br><span class="line">String S3 = “ simple”;</span><br><span class="line">String S4 = “ test”;</span><br><span class="line">String S1 = S2 +S3 + S4;</span><br></pre></td></tr></table></figure>
<p>这时候 JVM 会规规矩矩的按照原来的方式去做</p>
<p><a href="https://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/index.html" target="_blank" rel="noopener">https://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/index.html</a></p>
<p><a href="https://blog.csdn.net/qq_36357995/article/details/79985538" target="_blank" rel="noopener">https://blog.csdn.net/qq_36357995/article/details/79985538</a></p>
<p><a href="https://segmentfault.com/a/1190000004261063" target="_blank" rel="noopener">https://segmentfault.com/a/1190000004261063</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;常用的jdk的组件的源码分析之：&lt;code&gt;String&lt;/code&gt;,&lt;code&gt;StringBuffer&lt;/code&gt;,&lt;code&gt;StringBuilder&lt;/code&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="源码分析" scheme="http://www.aitozi.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
    
      <category term="jdk" scheme="http://www.aitozi.com/tags/jdk/"/>
    
  </entry>
  
  <entry>
    <title>rocksdb概念简介</title>
    <link href="http://www.aitozi.com/rocksdb-wiki.html"/>
    <id>http://www.aitozi.com/rocksdb-wiki.html</id>
    <published>2018-08-23T15:20:44.000Z</published>
    <updated>2019-03-14T17:07:11.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文翻译自：<a href="https://github.com/facebook/rocksdb/wiki/rocksdb-basics" target="_blank" rel="noopener">https://github.com/facebook/rocksdb/wiki/rocksdb-basics</a></p>
<p>主要是rocksdb的一些概念理解和介绍</p>
<a id="more"></a>
<p>rocksdb主要组成部分memtable,sstfile,logfile，rocksdb 支持将database切分成多个columnFamily，所有的数据库创建如果没有指定的话会是一个default column<br>他支持批量原子写入，key和value都是纯byte流，key和value的大小都没有做限制</p>
<p>所有database中的数据都是以一个有序的形式被放置（怎么做的呢？后append的数据怎么有序），应用可以指定key的comparison方法，来指定key的排序方式，Iterator API可以在database做一个RangeScan操作，他会指向一个特定的key，然后进行一个一个遍历。在调用iterator的时候会创建database的即时视图，因此所有查询的key都是一致的。</p>
<h3 id="Snapshot"><a href="#Snapshot" class="headerlink" title="Snapshot"></a>Snapshot</h3><p>Snapshot Api也支持创建database某一时间点的视图，Get和Iterator Api可以用以读取指定snapshot的数据，从某种意义上说，snapshot和iterator都会提供database的当前视图，但是他们的实现不同。iterator是短期的/前台线程的scan，而长期/后台的scan最好是通过snapshot。iterator会对所有底层与pint-in-time database视图相关的的文件保留一个引用计数，这些文件知道iterator结束之后才会被删除。然而snapshot不会阻碍文件的删除，取而代之的是在compaction的过程会意识到snapshots的存在，直接不会删除在已经存在于snapshot中的key。snapshot在database重启的会丢失，reload rocksdb library会释放所有的snapshot</p>
<h3 id="Prefix-Iterators"><a href="#Prefix-Iterators" class="headerlink" title="Prefix Iterators"></a>Prefix Iterators</h3><p>大多数基于LSM设计的存储引擎都不太能支持高效的RangeScan API，因为他需要查阅每个文件，但是真正的应用并不是纯粹的随机读取key，一般会以一个key-prefix去查询，rocksdb利用了这个特点做了一些优化。应用可以配置prefix_extractor来指定key-prefix，rocksdb决定，存储的blooms，iterator可以通过ReadOptios指定prefix，然后rocksDB将会使用这些bloom bits来避免查询那些不包含那些key-perfix开始的key的文件。</p>
<h3 id="Persistence"><a href="#Persistence" class="headerlink" title="Persistence"></a>Persistence</h3><p>rocksdb有一个事务日志，所有的puts操作会被存储在memtables中，同时也会可选的写入事务日志中，在重启的时候，会重新执行事务日志中的记录。事务日志可以配置和sst文件放在不同的目录。这是因为有时你并不想持久化数据文件，同时又可以将事务日志持久化到一个相对较慢的持久化存储中，来确保数据不会丢失。 每一个Put都有一个标志，通过WriteOptios来标志是否需要写入事务日志中，同时也可以配置是否需要同步等到数据已经被写入到事务日志完成之后才将Put操作标记为commit完成。</p>
<p>在内部实现中，RocksDB会使用batch-commit的机制去批量的将事务操作提交到事务日志中，所以在一次同步调用中会提交多个transactions</p>
<h3 id="Fault-Tolerance"><a href="#Fault-Tolerance" class="headerlink" title="Fault Tolerance"></a>Fault Tolerance</h3><p>Rocksdb使用checksum去检测存储是否有损坏</p>
<h3 id="Multi-Threaded-Compactions"><a href="#Multi-Threaded-Compactions" class="headerlink" title="Multi-Threaded Compactions"></a>Multi-Threaded Compactions</h3><p>compaction的存在是为了删除同一个key的多个副本，这种情况发生在用户更新了某个key的值，compaction也负责将要删除的key进行删除。整个database是存储在sstable中的，当memtable满了的时候会写入到Level-0（L0）的文件中，RocksDB在将数据从memtable flush到文件的时候会先将重复的key进行删除。然后一些文件会周期性的读入并形成更大的文件，这就是compaction的过程。</p>
<p>对于一个LSM的database的写入的吞吐量取决于compaction所能达到的速度，特别是数据存储在ssd或者RAM中。RocksDB可以配置为启用多个并发compaction线程。据观察，与单线程compaction相比，基于ssd的数据库的持续写入速率可能在多线程compaction的情况下增加10倍之多</p>
<h3 id="compaction-Styles"><a href="#compaction-Styles" class="headerlink" title="compaction Styles"></a>compaction Styles</h3><p>通常的style的compaction是完全基于排序的，运行与L0文件或者L1+. Compaction会挑选一些按时间顺序相邻文件，然后将其合并成一个新的sstable</p>
<p>level style compaction在数据库存储会分为多个等级，最近的数据存储在L0层，最老的数据存储在Lmax层，只有L0层会存在重叠的key，一次compaction会将Ln的file和Ln+1的file做compaction然后形成新的文件替换Ln+1的文件，Universal Style 和Level style相比通常会有较低的写入放大但是较高的磁盘占用和读放大</p>
<p>（写入放大）：<br><a href="https://www.zhihu.com/question/31024021" target="_blank" rel="noopener">https://www.zhihu.com/question/31024021</a><br><a href="https://www.wikiwand.com/zh-hans/%E5%86%99%E5%85%A5%E6%94%BE%E5%A4%A7" target="_blank" rel="noopener">https://www.wikiwand.com/zh-hans/%E5%86%99%E5%85%A5%E6%94%BE%E5%A4%A7</a></p>
<p>同时RocksDB也支持用户自定义compaction方式，可以通过<code>Options.disable_auto_compaction</code>关闭原生的compaction算法，同时<code>GetLiveFilesMetaData</code>接口可以让外置组件查看每一个database中的数据文件从而决定哪些数据需要merge和合并。通过调用<code>CompactFiles</code>来进行文件的合并，<code>DeleteFile</code>来进行文件的删除</p>
<h3 id="metadata-storage"><a href="#metadata-storage" class="headerlink" title="metadata storage"></a>metadata storage</h3><p>数据库中的MANIFEST文件记录了数据库的状态，compaction线程会新增新文件，删除旧文件，这些操作会通过 MANIFEST记录来持久化，同样记录操作也是用了batch-commit的算法来缓冲重复的对MANIFEST文件的同步写入</p>
<h3 id="Avoiding-Stalls"><a href="#Avoiding-Stalls" class="headerlink" title="Avoiding Stalls"></a>Avoiding Stalls</h3><p>后台的compaction线程会将memtable中的内容flush到文件中，如果所有的后台线程都忙着做长时间的compaction，那么突然一个大流量的写入可能就会把memtable写满，这就会导致新的写入被hung住，这个问题可以通过配置rocksdb保持特定的几个线程专门保留来进行flush操作</p>
<h3 id="Compaction-Filter"><a href="#Compaction-Filter" class="headerlink" title="Compaction Filter"></a>Compaction Filter</h3><p>一些应用可能在compaction的时候可能期望对key做出一些处理，比如数据库内部实现可能需要支持TTL，来删除过期的key，这可以通过自定义实现compaction filter来实现。他提供了用户在compaction的过程中修改key value以及丢弃这个key的数据的能力</p>
<h3 id="Read-only-mode"><a href="#Read-only-mode" class="headerlink" title="Read only mode"></a>Read only mode</h3><p>database可以以read only的模式打开这样数据库保证所有的数据都不可修改，同时也会极大的提升读性能，因为完全了避免了锁</p>
<h3 id="Full-Backups-Incremental-Backups-and-Replication"><a href="#Full-Backups-Incremental-Backups-and-Replication" class="headerlink" title="Full Backups, Incremental Backups and Replication"></a>Full Backups, Incremental Backups and Replication</h3><p>RocksDB支持增量的备份，BackupableDB使得Rocksdb的备份很简单，后续会深入介绍</p>
<p>增量的复制需要能够找到数据库最近的改变，GetUpdatesSince API支持应用tail最近的事务日志，因此他能够连续的获取事务日志，然后将其应用于远程的复制或者备份</p>
<p>未完待续</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文翻译自：&lt;a href=&quot;https://github.com/facebook/rocksdb/wiki/rocksdb-basics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/facebook/rocksdb/wiki/rocksdb-basics&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;主要是rocksdb的一些概念理解和介绍&lt;/p&gt;
    
    </summary>
    
      <category term="刨根问底" scheme="http://www.aitozi.com/categories/%E5%88%A8%E6%A0%B9%E9%97%AE%E5%BA%95/"/>
    
    
      <category term="RocksDB" scheme="http://www.aitozi.com/tags/RocksDB/"/>
    
  </entry>
  
  <entry>
    <title>flink中状态实现的深入理解</title>
    <link href="http://www.aitozi.com/flink-state.html"/>
    <id>http://www.aitozi.com/flink-state.html</id>
    <published>2018-08-04T08:35:29.000Z</published>
    <updated>2018-08-05T04:42:13.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文是源于要在内部分享，所以提前整理了一些flink中的状态的一些知识，flink状态所包含的东西很多，在下面列举了一些，还有一<br>些在本文没有体现，后续会单独的挑出来再进行讲解</p>
<a id="more"></a>
<ul>
<li>state的层次结构</li>
<li>keyedState =&gt; windowState</li>
<li>OperatorState =&gt; kafkaOffset</li>
<li>stateBackend</li>
<li>snapshot/restore</li>
<li><em>internalTimerService</em></li>
<li><strong>RocksDB操作的初探</strong></li>
<li><em>state ttL</em></li>
<li><em>state local recovery</em></li>
<li><strong>QueryableState</strong></li>
<li><strong>increamental checkpoint</strong></li>
<li>state redistribution</li>
<li><em>broadcasting state</em></li>
<li><strong>CheckpointStreamFactory</strong></li>
</ul>
<hr>
<h3 id="内部和外部状态"><a href="#内部和外部状态" class="headerlink" title="内部和外部状态"></a>内部和外部状态</h3><p>flink状态分为了内部和外部使用接口，但是两个层级都是一一对应，内部接口都实现了外部接口，主要是有两个目的</p>
<ul>
<li>内部接口提供了更多的方法，包括获取state中的serialize之后的byte，以及Namespace的操作方法。内部状态主要用于内部runtime实现时所需要用到的一些状态比如window中的windowState，CEP中的sharedBuffer,kafkaConsumer中offset管理的ListState,而外部State接口主要是用户自定义使用的一些状态</li>
<li>考虑到各个版本的兼容性，外部接口要保障跨版本之间的兼容问题，而内部接口就很少受到这个限制，因此也就比较灵活</li>
</ul>
<p>层次结构图：</p>
<p><img src="http://or0igopk2.bkt.clouddn.com/18-8-2/82936981.jpg" alt></p>
<h3 id="状态的使用"><a href="#状态的使用" class="headerlink" title="状态的使用"></a>状态的使用</h3><p>了解了flink 状态的层次结构，那么编程中和flink内部是如何使用这些状态呢？</p>
<p>flink中使用状态主要是两部分，一部分是函数中使用状态，另一部分是在operator中使用状态</p>
<p>方式：</p>
<ul>
<li>CheckpointedFunction</li>
<li>ListCheckpointed</li>
<li>RuntimeContext （DefaultKeyedStateStore）</li>
<li>StateContext</li>
</ul>
<p>StateContext</p>
<p>StateInitializationContext</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></pre></td><td class="code"><pre><span class="line">Iterable&lt;StatePartitionStreamProvider&gt; getRawOperatorStateInputs();</span><br><span class="line"></span><br><span class="line">Iterable&lt;KeyGroupStatePartitionStreamProvider&gt; getRawKeyedStateInputs();</span><br></pre></td></tr></table></figure>
<p>ManagedInitializationContext</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">OperatorStateStore getOperatorStateStore();</span><br><span class="line">KeyedStateStore getKeyedStateStore();</span><br></pre></td></tr></table></figure>
<p>举例：</p>
<ol>
<li><p>AbstractStreamOperator封装了这个方法<code>initializeState(StateInitializationContext context)</code>用以在operator中进行raw和managed的状态管理</p>
</li>
<li><p>CheckpointedFunction的用法其实也是借助于StateContext进行相关实现</p>
</li>
</ol>
<p><code>CheckpointedFunction#initializeState</code>方法在transformation function的各个并发实例初始化的时候被调用这个方法提供了<code>FunctionInitializationContext</code>的对象，可以通过这个<code>context</code>来获取<code>OperatorStateStore</code>或者<code>KeyedStateStore</code>，也就是说通过这个接口可以注册这两种类型的State，这也是和ListCheckpointed接口不一样的地方，只是说<code>KeyedStateStore</code>只能在keyedstream上才能注册，否则就会报错而已,以下是一个使用这两种类型状态的样例。 可以参见<code>FlinkKafkaConsumerBase</code>通过这个接口来实现offset的管理。</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><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></pre></td><td class="code"><pre><span class="line">public class MyFunction&lt;T&gt; implements MapFunction&lt;T, T&gt;, CheckpointedFunction &#123;</span><br><span class="line"></span><br><span class="line">     private ReducingState&lt;Long&gt; countPerKey;</span><br><span class="line">     private ListState&lt;Long&gt; countPerPartition;</span><br><span class="line"></span><br><span class="line">     private long localCount;</span><br><span class="line"></span><br><span class="line">     public void initializeState(FunctionInitializationContext context) throws Exception &#123;</span><br><span class="line">         // get the state data structure for the per-key state</span><br><span class="line">         countPerKey = context.getKeyedStateStore().getReducingState(</span><br><span class="line">                 new ReducingStateDescriptor&lt;&gt;(&quot;perKeyCount&quot;, new AddFunction&lt;&gt;(), Long.class));</span><br><span class="line"></span><br><span class="line">         // get the state data structure for the per-partition state</span><br><span class="line">         countPerPartition = context.getOperatorStateStore().getOperatorState(</span><br><span class="line">                 new ListStateDescriptor&lt;&gt;(&quot;perPartitionCount&quot;, Long.class));</span><br><span class="line"></span><br><span class="line">         // initialize the &quot;local count variable&quot; based on the operator state</span><br><span class="line">         for (Long l : countPerPartition.get()) &#123;</span><br><span class="line">             localCount += l;</span><br><span class="line">         &#125;</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">     public void snapshotState(FunctionSnapshotContext context) throws Exception &#123;</span><br><span class="line">         // the keyed state is always up to date anyways</span><br><span class="line">         // just bring the per-partition state in shape</span><br><span class="line">         countPerPartition.clear();</span><br><span class="line">         countPerPartition.add(localCount);</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">     public T map(T value) throws Exception &#123;</span><br><span class="line">         // update the states</span><br><span class="line">         countPerKey.add(1L);</span><br><span class="line">         localCount++;</span><br><span class="line"></span><br><span class="line">         return value;</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>这个Context的继承接口StateSnapshotContext的方法则提供了raw state的存储方法，但是其实没有对用户函数提供相应的接口，只是在引擎中有相关的使用，相比较而言这个接口提供的方法，context比较多，也有一些简单的方法去注册使用operatorstate 和 keyedState。如通过<code>RuntimeContext</code>注册keyedState: </p>
<p>因此使用简易化程度为:</p>
<blockquote>
<p>RuntimeContext &gt; FunctionInitializationContext &gt; StateSnapshotContext</p>
</blockquote>
<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><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></pre></td><td class="code"><pre><span class="line">keyedStream.map(new RichFlatMapFunction&lt;MyType, List&lt;MyType&gt;&gt;() &#123;</span><br><span class="line"></span><br><span class="line">     private ListState&lt;MyType&gt; state;</span><br><span class="line"></span><br><span class="line">     public void open(Configuration cfg) &#123;</span><br><span class="line">         state = getRuntimeContext().getListState(</span><br><span class="line">                 new ListStateDescriptor&lt;&gt;(&quot;myState&quot;, MyType.class));</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">     public void flatMap(MyType value, Collector&lt;MyType&gt; out) &#123;</span><br><span class="line">         if (value.isDivider()) &#123;</span><br><span class="line">             for (MyType t : state.get()) &#123;</span><br><span class="line">                 out.collect(t);</span><br><span class="line">             &#125;</span><br><span class="line">         &#125; else &#123;</span><br><span class="line">             state.add(value);</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>ListCheckpointed</code>来注册OperatorState，但是这个有限制：<br>一个function只能注册一个state，因为并不能像其他接口一样指定state的名字. </p>
<p>example：</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><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">public class CountingFunction&lt;T&gt; implements MapFunction&lt;T, Tuple2&lt;T, Long&gt;&gt;, ListCheckpointed&lt;Long&gt; &#123;</span><br><span class="line"></span><br><span class="line">     // this count is the number of elements in the parallel subtask</span><br><span class="line">     private long count;</span><br><span class="line"></span><br><span class="line">     &#123;@literal @&#125;Override</span><br><span class="line">     public List&lt;Long&gt; snapshotState(long checkpointId, long timestamp) &#123;</span><br><span class="line">         // return a single element - our count</span><br><span class="line">         return Collections.singletonList(count);</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">     &#123;@literal @&#125;Override</span><br><span class="line">     public void restoreState(List&lt;Long&gt; state) throws Exception &#123;</span><br><span class="line">         // in case of scale in, this adds up counters from different original subtasks</span><br><span class="line">         // in case of scale out, list this may be empty</span><br><span class="line">         for (Long l : state) &#123;</span><br><span class="line">             count += l;</span><br><span class="line">         &#125;</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">     &#123;@literal @&#125;Override</span><br><span class="line">     public Tuple2&lt;T, Long&gt; map(T value) &#123;</span><br><span class="line">         count++;</span><br><span class="line">         return new Tuple2&lt;&gt;(value, count);</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>下面比较一下里面的两种stateStore</p>
<ul>
<li>KeyedStateStore</li>
<li>OperatorStateStore</li>
</ul>
<p>查看OperatorStateStore接口可以看到OperatorState只提供了ListState一种形式的状态接口,OperatorState和KeyedState主要有以下几个区别：</p>
<ul>
<li>keyedState只能应用于KeyedStream，而operatorState都可以</li>
<li>keyedState可以理解成一个算子为每个subtask的每个key维护了一个状态namespace，而OperatorState是每个subtask共享一个状态</li>
<li>operatorState只提供了ListState，而keyedState提供了<code>ValueState</code>,<code>ListState</code>,<code>ReducingState</code>,<code>MapState</code></li>
<li>operatorStateStore的默认实现只有<code>DefaultOperatorStateBackend</code>可以看到他的状态都是存储在堆内存之中，而keyedState根据backend配置的不同，线上都是存储在rocksdb之中</li>
</ul>
<h3 id="snapshot"><a href="#snapshot" class="headerlink" title="snapshot"></a>snapshot</h3><p>这个让我们着眼于两个Operator的snapshot，<code>AbstractStreamOperator</code> 和 <code>AbstractUdfStreamOperator</code>,这两个基类几乎涵盖了所有相关operator和function在做snapshot的时候会做的处理。</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></pre></td><td class="code"><pre><span class="line">if (null != operatorStateBackend) &#123;</span><br><span class="line">				snapshotInProgress.setOperatorStateManagedFuture(</span><br><span class="line">					operatorStateBackend.snapshot(checkpointId, timestamp, factory, checkpointOptions));</span><br><span class="line">			&#125;</span><br><span class="line"></span><br><span class="line">			if (null != keyedStateBackend) &#123;</span><br><span class="line">				snapshotInProgress.setKeyedStateManagedFuture(</span><br><span class="line">					keyedStateBackend.snapshot(checkpointId, timestamp, factory, checkpointOptions));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ol>
<li>按keyGroup去snapshot各个timerService的状态，包括processingTimer和eventTimer（RawKeyedOperatorState）</li>
<li>将operatorStateBackend和keyedStateBackend中的状态做snapshot</li>
<li>如果Operator还包含了userFunction，即是一个<code>UdfStreamOperator</code>,那么可以注意到udfStreamOperator覆写了父类的<code>snapshotState(StateSnapshotContext context)</code>方法，其主要目的就是为了将Function中的状态及时的register到相应的backend中，在第二步的时候统一由<code>CheckpointStreamFactory</code>去做快照</li>
</ol>
<h4 id="StreamingFunctionUtils-snapshotFunctionState"><a href="#StreamingFunctionUtils-snapshotFunctionState" class="headerlink" title="StreamingFunctionUtils#snapshotFunctionState"></a>StreamingFunctionUtils#snapshotFunctionState</h4><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><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">if (userFunction instanceof CheckpointedFunction) &#123;</span><br><span class="line">			((CheckpointedFunction) userFunction).snapshotState(context);</span><br><span class="line"></span><br><span class="line">			return true;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		if (userFunction instanceof ListCheckpointed) &#123;</span><br><span class="line">			@SuppressWarnings(&quot;unchecked&quot;)</span><br><span class="line">			List&lt;Serializable&gt; partitionableState = ((ListCheckpointed&lt;Serializable&gt;) userFunction).</span><br><span class="line">				snapshotState(context.getCheckpointId(), context.getCheckpointTimestamp());</span><br><span class="line"></span><br><span class="line">			ListState&lt;Serializable&gt; listState = backend.</span><br><span class="line">				getSerializableListState(DefaultOperatorStateBackend.DEFAULT_OPERATOR_STATE_NAME);</span><br><span class="line"></span><br><span class="line">			listState.clear();</span><br><span class="line"></span><br><span class="line">			if (null != partitionableState) &#123;</span><br><span class="line">				try &#123;</span><br><span class="line">					for (Serializable statePartition : partitionableState) &#123;</span><br><span class="line">						listState.add(statePartition);</span><br><span class="line">					&#125;</span><br><span class="line">				&#125; catch (Exception e) &#123;</span><br><span class="line">					listState.clear();</span><br><span class="line"></span><br><span class="line">					throw new Exception(&quot;Could not write partitionable state to operator &quot; +</span><br><span class="line">						&quot;state backend.&quot;, e);</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到这里就只有以上分析的两种类型的checkpoined接口，<code>CheckpointedFunction</code>，只需要执行相应的snapshot方法，相应的函数就已经将要做snapshot的数据打入了相应的state中，而<code>ListCheckpointed</code>接口由于返回的是个List，所以需要手动的通过<code>getSerializableListState</code>注册一个<code>ListState</code>(<em>这也是ListCheckpointed只能注册一个state的原因</em>),然后将List数据挨个存入ListState中。</p>
<h4 id="operatorStateBackend-snapshot"><a href="#operatorStateBackend-snapshot" class="headerlink" title="operatorStateBackend#snapshot"></a>operatorStateBackend#snapshot</h4><ol>
<li>针对所有注册的state作deepCopy,为了防止在checkpoint的时候数据结构又被修改，deepcopy其实是通过序列化和反序列化的过程（参见<a href="http://aitozi.com/java-serialization.html" target="_blank" rel="noopener">http://aitozi.com/java-serialization.html</a>）</li>
<li>异步将state以及metainfo的数据写入到hdfs中，使用的是flink的asyncIO（这个也可以后续深入了解下），并返回相应的statehandle用作restore的过程</li>
<li>在StreamTask触发checkpoint的时候会将一个Task中所有的operator触发一次snapshot，触发部分就是上面1，2两个步骤，其中第二步是会返回一个RunnableFuture，在触发之后会提交一个<code>AsyncCheckpointRunnable</code>异步任务，会阻塞一直等到checkpoint的<code>Future</code>，其实就是去调用这个方法<code>AbstractAsyncIOCallable</code>, 直到完成之后OperatorState会返回一个<code>OperatorStateHandle</code>,这个地方和后文的keyedState返回的handle不一样。</li>
</ol>
<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><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">@Override</span><br><span class="line">	public V call() throws Exception &#123;</span><br><span class="line"></span><br><span class="line">		synchronized (this) &#123;</span><br><span class="line">			if (isStopped()) &#123;</span><br><span class="line">				throw new IOException(&quot;Task was already stopped. No I/O handle opened.&quot;);</span><br><span class="line">			&#125;</span><br><span class="line"></span><br><span class="line">			ioHandle = openIOHandle();</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		try &#123;</span><br><span class="line"></span><br><span class="line">			return performOperation();</span><br><span class="line"></span><br><span class="line">		&#125; finally &#123;</span><br><span class="line">			closeIOHandle();</span><br><span class="line">		&#125;</span><br></pre></td></tr></table></figure>
<p>在managed keyedState、managed operatorState、raw keyedState、和raw operatorState都完成返回相应的Handle之后，会生成一个SubTaskState来ack jobmanager,这个主要是用在restore的过程中</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">SubtaskState subtaskState = createSubtaskStateFromSnapshotStateHandles(</span><br><span class="line">					chainedNonPartitionedOperatorsState,</span><br><span class="line">					chainedOperatorStateBackend,</span><br><span class="line">					chainedOperatorStateStream,</span><br><span class="line">					keyedStateHandleBackend,</span><br><span class="line">					keyedStateHandleStream);</span><br><span class="line">					</span><br><span class="line">owner.getEnvironment().acknowledgeCheckpoint(</span><br><span class="line">	checkpointMetaData.getCheckpointId(),</span><br><span class="line">	checkpointMetrics,</span><br><span class="line">	subtaskState);</span><br></pre></td></tr></table></figure>
<p>在jm端，ack的时候又将各个handle封装在<code>pendingCheckpoint =&gt; operatorStates =&gt; operatorState =&gt; operatorSubtaskState</code>中,最后无论是savepoint或者是externalCheckpoint都会将相应的handle序列化存储到hdfs，这也就是所谓的checkpoint元数据。这个可以起个任务观察下zk和hdfs上的文件，补充一下相关的验证。</p>
<p>至此完成operator state的snapshot/checkpoint阶段</p>
<h4 id="KeyedStateBackend-snapshot"><a href="#KeyedStateBackend-snapshot" class="headerlink" title="KeyedStateBackend#snapshot"></a>KeyedStateBackend#snapshot</h4><p>和operatorStateBackend一样，snapshot也分为了同步和异步两个部分。</p>
<ol>
<li>rocksDB的keyedStateBackend的snapshot提供了增量和全量两种方式</li>
<li>利用rocksdb自身的snapshot进行<code>this.snapshot = stateBackend.db.getSnapshot();</code> 这个过程是同步的，rocksdb这块是怎么snapshot还不是很了解，待后续学习</li>
<li>之后也是一样异步将数据写入hdfs，返回相应的keyGroupsStateHandle <code>snapshotOperation.closeCheckpointStream();</code> </li>
</ol>
<p>不同的地方在于增量返回的是<code>IncrementalKeyedStateHandle</code>,而全量返回的是<code>KeyGroupsStateHandle</code>，</p>
<h3 id="restore-redistribution"><a href="#restore-redistribution" class="headerlink" title="restore / redistribution"></a>restore / redistribution</h3><h4 id="OperatorState的rescale"><a href="#OperatorState的rescale" class="headerlink" title="OperatorState的rescale"></a>OperatorState的rescale</h4><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">void setInitialState(TaskStateHandles taskStateHandles) throws Exception;</span><br></pre></td></tr></table></figure>
<p>一个task在真正的执行任务之前所需要做的事情是把状态inject到task中，如果一个任务是失败之后从上次的checkpoint点恢复的话，他的状态就是非空的。streamTask也就靠是否有这样的一个恢复状态来确认算子是不是在restore来branch他的启动逻辑</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><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">if (null != taskStateHandles) &#123;</span><br><span class="line">		if (invokable instanceof StatefulTask) &#123;</span><br><span class="line">			StatefulTask op = (StatefulTask) invokable;</span><br><span class="line">			op.setInitialState(taskStateHandles);</span><br><span class="line">		&#125; else &#123;</span><br><span class="line">			throw new IllegalStateException(&quot;Found operator state for a non-stateful task invokable&quot;);</span><br><span class="line">		&#125;</span><br><span class="line">		// be memory and GC friendly - since the code stays in invoke() for a potentially long time,</span><br><span class="line">		// we clear the reference to the state handle</span><br><span class="line">		//noinspection UnusedAssignment</span><br><span class="line">		taskStateHandles = null;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>那么追根究底一下这个Handle是怎么带入的呢？</p>
<p><code>FixedDelayRestartStrategy =&gt; triggerFullRecovery =&gt; Execution#restart =&gt; Execution#scheduleForExecution =&gt; Execution#deployToSlot =&gt; ExecutionVertex =&gt; TaskDeploymentDescriptor =&gt; taskmanger =&gt; task</code></p>
<p>当然还有另一个途径就是通过向jobmanager submitJob的时候带入restore的checkpoint path， 这两种方式最终都会通过<code>checkpointCoordinator#restoreLatestCheckpointedState</code>来恢复hdfs中的状态来获取到snapshot时候存入的StateHandle。</p>
<p>恢复的过程如何进行redistribution呢？  也就是大家关心的并发度变了我的状态的行为是怎么样的。</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">// re-assign the task states</span><br><span class="line">final Map&lt;OperatorID, OperatorState&gt; operatorStates = latest.getOperatorStates();</span><br><span class="line"></span><br><span class="line">StateAssignmentOperation stateAssignmentOperation =</span><br><span class="line">		new StateAssignmentOperation(tasks, operatorStates, allowNonRestoredState);</span><br><span class="line"></span><br><span class="line">stateAssignmentOperation.assignStates();</span><br></pre></td></tr></table></figure>
<ol>
<li>如果并发度没变那么不做重新的assign，除非state的模式是broadcast，会将一个task的state广播给所有的task</li>
<li>对于operator state会针对每一个name的state计算出每个subtask中的element个数之和（这就要求每个element之间相互独立）进行roundrobin分配</li>
<li>keyedState的重新分配相对简单，就是根据新的并发度和最大并发度计算新的keygroupRange，然后根据subtaskIndex获取keyGroupRange，然后获取到相应的keyStateHandle完成状态的切分。</li>
</ol>
<p>这里补充关于raw state和managed state在rescale上的差别，由于operator state在reassign的时候是根据metaInfo来计算出所有的List<element>来重新分配，operatorbackend中注册的状态是会保存相应的metainfo，最终也会在snapshot的时候存入OperatorHandle，那raw state的metainfo是在哪里呢？</element></p>
<p>其实会在写入hdfs返回相应的handle的时候构建一个默认的，<code>OperatorStateCheckpointOutputStream#closeAndGetHandle</code>,其中状态各个partition的构建来自<code>startNewPartition</code>方法，引擎中我所看到的rawstate仅有timerservice的raw keyedState </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><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">OperatorStateHandle closeAndGetHandle() throws IOException &#123;</span><br><span class="line">		StreamStateHandle streamStateHandle = delegate.closeAndGetHandle();</span><br><span class="line"></span><br><span class="line">		if (null == streamStateHandle) &#123;</span><br><span class="line">			return null;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		if (partitionOffsets.isEmpty() &amp;&amp; delegate.getPos() &gt; initialPosition) &#123;</span><br><span class="line">			startNewPartition();</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		Map&lt;String, OperatorStateHandle.StateMetaInfo&gt; offsetsMap = new HashMap&lt;&gt;(1);</span><br><span class="line"></span><br><span class="line">		OperatorStateHandle.StateMetaInfo metaInfo =</span><br><span class="line">				new OperatorStateHandle.StateMetaInfo(</span><br><span class="line">						partitionOffsets.toArray(),</span><br><span class="line">						OperatorStateHandle.Mode.SPLIT_DISTRIBUTE);</span><br><span class="line"></span><br><span class="line">		offsetsMap.put(DefaultOperatorStateBackend.DEFAULT_OPERATOR_STATE_NAME, metaInfo);</span><br><span class="line"></span><br><span class="line">		return new OperatorStateHandle(offsetsMap, streamStateHandle);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>
<h4 id="KeyedState的keyGroup"><a href="#KeyedState的keyGroup" class="headerlink" title="KeyedState的keyGroup"></a>KeyedState的keyGroup</h4><p>keyedState重新分配里引入了一个keyGroup的概念，那么这里为什么要引入keygroup这个概念呢？</p>
<p><img src="http://or0igopk2.bkt.clouddn.com/18-8-3/53377760.jpg" alt></p>
<p><img src="http://or0igopk2.bkt.clouddn.com/18-8-3/18711916.jpg" alt></p>
<ol>
<li>hash(key) = key(identity)</li>
<li>key_group(key) = hash(key) % number_of_key_groups (等于最大并发)，默认flink任务会设置一个max parallel</li>
<li>subtask(key) = key_greoup(key) * parallel / number_of_key_groups</li>
</ol>
<ul>
<li>避免在恢复的时候带来随机IO</li>
<li>避免每个subtask需要将所有的状态数据读取出来pick和自己subtask相关的浪费了很多io资源</li>
<li>减少元数据的量，不再需要保存每次的key，每一个keygroup组只需保留一个range</li>
</ul>
<p>实际实现上的keyGroup range和上图有区别，是连续的:</p>
<p>比如：subtask1: [0-10], subtask2: [11-12] …</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></pre></td><td class="code"><pre><span class="line">int start = operatorIndex == 0 ? 0 : ((operatorIndex * maxParallelism - 1) / parallelism) + 1;</span><br><span class="line">int end = ((operatorIndex + 1) * maxParallelism - 1) / parallelism;</span><br><span class="line">return new KeyGroupRange(start, end);</span><br></pre></td></tr></table></figure>
<ul>
<li>每一个backend（subtask）上只有一个keygroup range</li>
<li>每一个subtask在restore的时候就接收到了已经分配好的和重启后当前这个并发相绑定的keyStateHandle</li>
</ul>
<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">subManagedKeyedState = getManagedKeyedStateHandles(operatorState, keyGroupPartitions.get(subTaskIndex));</span><br><span class="line">subRawKeyedState = getRawKeyedStateHandles(operatorState, keyGroupPartitions.get(subTaskIndex));</span><br></pre></td></tr></table></figure>
<p>这里面关键的一步在于，根据新的subtask上的keyGroupRange，从原来的operator的keyGroupsStateHandle中求取本subtask所关心的一部分Handle，可以看到每个KeyGroupsStateHandle都维护了<code>KeyGroupRangeOffsets</code>这样一个变量，来标记这个handle所覆盖的keygrouprange，以及keygrouprange在stream中offset的位置，可以看下再snapshot的时候会记录offset到这个对象中来</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">keyGroupRangeOffsets.setKeyGroupOffset(mergeIterator.keyGroup(), outStream.getPos());</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><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">public KeyGroupRangeOffsets getIntersection(KeyGroupRange keyGroupRange) &#123;</span><br><span class="line">		Preconditions.checkNotNull(keyGroupRange);</span><br><span class="line">		KeyGroupRange intersection = this.keyGroupRange.getIntersection(keyGroupRange);</span><br><span class="line">		long[] subOffsets = new long[intersection.getNumberOfKeyGroups()];</span><br><span class="line">		if(subOffsets.length &gt; 0) &#123;</span><br><span class="line">			System.arraycopy(</span><br><span class="line">					offsets,</span><br><span class="line">					computeKeyGroupIndex(intersection.getStartKeyGroup()),</span><br><span class="line">					subOffsets,</span><br><span class="line">					0,</span><br><span class="line">					subOffsets.length);</span><br><span class="line">		&#125;</span><br><span class="line">		return new KeyGroupRangeOffsets(intersection, subOffsets);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>
<p>KeyGroupsStateHandle是一个subtask的所有state的一个handle<br>KeyGroupsStateHandle维护一个KeyGroupRangeOffsets，<br>KeyGroupRangeOffsets维护一个KeyGroupRange和offsets<br>KeyGroupRange维护多个KeyGroup<br>KeyGroup维护多个key</p>
<p>KeyGroupsStateHandle和operatorStateHandle还有一个不同点，operatorStateHandle维护了metainfo中的offset信息用在restore时的reassign，原因在于KeyGroupsStateHandle的reassign不依赖这些信息，当然在restore的时候也需要keygroupOffset中的offset信息来重新构建keyGroupsStateHandle来进行各个task的状态分配。</p>
<p>参考：</p>
<p><a href="https://flink.apache.org/features/2017/07/04/flink-rescalable-state.html" target="_blank" rel="noopener">https://flink.apache.org/features/2017/07/04/flink-rescalable-state.html</a></p>
<p><a href="http://chenyuzhao.me/2017/12/24/Flink-%E5%88%86%E5%B8%83%E5%BC%8F%E5%BF%AB%E7%85%A7%E7%9A%84%E8%AE%BE%E8%AE%A1-%E5%AD%98%E5%82%A8/" target="_blank" rel="noopener">http://chenyuzhao.me/2017/12/24/Flink-%E5%88%86%E5%B8%83%E5%BC%8F%E5%BF%AB%E7%85%A7%E7%9A%84%E8%AE%BE%E8%AE%A1-%E5%AD%98%E5%82%A8/</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文是源于要在内部分享，所以提前整理了一些flink中的状态的一些知识，flink状态所包含的东西很多，在下面列举了一些，还有一&lt;br&gt;些在本文没有体现，后续会单独的挑出来再进行讲解&lt;/p&gt;
    
    </summary>
    
      <category term="源码分析" scheme="http://www.aitozi.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
    
      <category term="Flink" scheme="http://www.aitozi.com/tags/Flink/"/>
    
  </entry>
  
  <entry>
    <title>Protobuf深入理解</title>
    <link href="http://www.aitozi.com/dig-protobuf.html"/>
    <id>http://www.aitozi.com/dig-protobuf.html</id>
    <published>2018-07-28T15:28:01.000Z</published>
    <updated>2019-03-14T17:06:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文带你深入理解和使用protobuf</p>
<a id="more"></a>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Protocol Buffers 是一种轻便高效的结构化数据存储格式，可以用于结构化数据串行化，或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python、Go 等语言的 API</p>
<p>使用protobuf需要这样几个步骤：</p>
<ul>
<li>在<code>.proto</code>文件中定义消息的格式</li>
<li>通过protoBuffer compiler编译生成相应的java类</li>
<li>通过Java protocol buffer Api来write和read相关的对象</li>
</ul>
<p>关于PB的操作方式见： <a href="https://developers.google.com/protocol-buffers/docs/javatutorial" target="_blank" rel="noopener">https://developers.google.com/protocol-buffers/docs/javatutorial</a> </p>
<hr>
<p>protobuf的序列化快的原因主要在于其编码实现和封解包的速度</p>
<h2 id="Protobuf编码"><a href="#Protobuf编码" class="headerlink" title="Protobuf编码"></a>Protobuf编码</h2><h3 id="Base-128-Varints-编码"><a href="#Base-128-Varints-编码" class="headerlink" title="Base 128 Varints 编码"></a>Base 128 Varints 编码</h3><p>数据传输中出于IO的考虑，我们会希望尽可能的对数据进行压缩。<br>Varint就是一种对数字进行编码的方法，编码后二进制数据是不定长的，数值越小的数字使用的字节数越少。例如对于int32_t，采用Varint编码后需要1~5个bytes，小的数字使用1个byte，大的数字使用5个bytes。基于实际场景中小数字的使用远远多于大数字，因此通过Varint编码对于大部分场景都可以起到一个压缩的效果。Varint的主要想法就是以标志位替换掉高字节的若干个0</p>
<p>下图是数字131415的variant编码,通过3个字节来表示131415<br><img src="http://or0igopk2.bkt.clouddn.com/18-7-28/50478975.jpg" alt></p>
<p>其中第一个字节的高位msb（Most Significant Bit ）为1表示下一个字节还有有效数据，msb为0表示该字节中的后7为是最后一组有效数字。踢掉最高位后的有效位组成真正的数字。注意到最终计算前将两个 byte 的位置相互交换过一次，这是因为 Google Protocol Buffer 字节序采用 little-endian（即低位字节排放在内存的低地址端） 的方式</p>
<p>从上面可以看出，variant编码存储比较小的整数时很节省空间，小于等于127的数字可以用一个字节存储。但缺点是对于大于</p>
<p>268,435,455（0xfffffff）的整数需要5个字节来存储。但是一般情况下（尤其在tag编码中）不会存储这么大的整数。</p>
<p>关于int32的varint编码代码</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">char</span>* <span class="title">EncodeVarint32</span><span class="params">(<span class="keyword">char</span>* dst, <span class="keyword">uint32_t</span> v)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// Operate on characters as unsigneds</span></span><br><span class="line">  <span class="keyword">unsigned</span> <span class="keyword">char</span>* ptr = <span class="keyword">reinterpret_cast</span>&lt;<span class="keyword">unsigned</span> <span class="keyword">char</span>*&gt;(dst);</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> B = <span class="number">128</span>;</span><br><span class="line">  <span class="keyword">if</span> (v &lt; (<span class="number">1</span>&lt;&lt;<span class="number">7</span>)) &#123;</span><br><span class="line">    *(ptr++) = v;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (v &lt; (<span class="number">1</span>&lt;&lt;<span class="number">14</span>)) &#123;</span><br><span class="line">    *(ptr++) = v | B;</span><br><span class="line">    *(ptr++) = v&gt;&gt;<span class="number">7</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (v &lt; (<span class="number">1</span>&lt;&lt;<span class="number">21</span>)) &#123;</span><br><span class="line">    *(ptr++) = v | B;</span><br><span class="line">    *(ptr++) = (v&gt;&gt;<span class="number">7</span>) | B;</span><br><span class="line">    *(ptr++) = v&gt;&gt;<span class="number">14</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (v &lt; (<span class="number">1</span>&lt;&lt;<span class="number">28</span>)) &#123;</span><br><span class="line">    *(ptr++) = v | B;</span><br><span class="line">    *(ptr++) = (v&gt;&gt;<span class="number">7</span>) | B;</span><br><span class="line">    *(ptr++) = (v&gt;&gt;<span class="number">14</span>) | B;</span><br><span class="line">    *(ptr++) = v&gt;&gt;<span class="number">21</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    *(ptr++) = v | B;</span><br><span class="line">    *(ptr++) = (v&gt;&gt;<span class="number">7</span>) | B;</span><br><span class="line">    *(ptr++) = (v&gt;&gt;<span class="number">14</span>) | B;</span><br><span class="line">    *(ptr++) = (v&gt;&gt;<span class="number">21</span>) | B;</span><br><span class="line">    *(ptr++) = v&gt;&gt;<span class="number">28</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">reinterpret_cast</span>&lt;<span class="keyword">char</span>*&gt;(ptr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="Message-Structure编码"><a href="#Message-Structure编码" class="headerlink" title="Message Structure编码"></a>Message Structure编码</h3><p>protocol buffer 中 message 是一系列键值对。message 的二进制版本只是使用字段号(field’s number 和 wire_type)作为 key。每个字段的名称和声明类型只能在解码端通过引用消息类型的定义（即 .proto 文件）来确定。这一点也是人们常常说的 protocol buffer 比 JSON，XML 安全一点的原因，如果没有数据结构描述 .proto 文件，拿到数据以后是无法解释成正常的数据的。</p>
<p>当消息编码时，键和值被连接成一个字节流。当消息被解码时，解析器需要能够跳过它无法识别的字段。这样，可以将新字段添加到消息中，而不会破坏不知道它们的旧程序。这就是所谓的 “向后”兼容性。</p>
<p>为此，线性的格式消息中每对的“key”实际上是两个值，其中一个是来自.proto文件的字段编号，加上提供正好足够的信息来查找下一个值的长度。在大多数语言实现中，这个 key 被称为 tag</p>
<p>wireType</p>
<p><img src="http://or0igopk2.bkt.clouddn.com/18-7-28/87891770.jpg" alt></p>
<p>key 的计算方法是 (field_number &lt;&lt; 3) | wire_type，换句话说，key 的最后 3 位表示的就是 wire_type。因此这里也涉及到前面proto文件定义的时候的宗旨，尽量将频繁使用的字段的字段号设置成1-15之间的数值，避免位数开销。这里的key的存储也是用了varint的方式</p>
<p><img src="http://or0igopk2.bkt.clouddn.com/18-7-28/94576622.jpg" alt></p>
<p>举例，一般 message 的字段号都是 1 开始的，所以对应的 tag 可能是这样的：</p>
<p><code>000 1000</code></p>
<p>末尾3位表示的是value的类型，这里是000，即0，代表的是varint值。右移3位，即0001，这代表的就是字段号(field number)。tag的例子就举这么多，接下来举一个 value的例子，还是用varint来举例：</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></pre></td><td class="code"><pre><span class="line">96 01 = 1001 0110  0000 0001</span><br><span class="line">       → 000 0001  ++  001 0110 (drop the msb and reverse the groups of 7 bits)</span><br><span class="line">       → 10010110</span><br><span class="line">       → 128 + 16 + 4 + 2 = 150</span><br></pre></td></tr></table></figure>
<p>所以 96 01 代表的数据就是 150 。</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></pre></td><td class="code"><pre><span class="line">message Test1 &#123;</span><br><span class="line">  required int32 a = 1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果存在上面这样的一个 message 的结构，如果存入 150，在 Protocol Buffer 中显示的二进制应该为 08 96 01 <code>varint(1 &lt;&lt; 3 | 0) = 0x08</code>.</p>
<p>注意到varint编码也应用在了key的计算上，使用非常频繁，或许是基于这个原因，pb里实现了一种性能更高的方案（coded_stream.cc）</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><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">inline uint8* CodedOutputStream::WriteVarint32FallbackToArrayInline(</span><br><span class="line">    uint32 value, uint8* target) &#123;</span><br><span class="line">  target[0] = static_cast&lt;uint8&gt;(value | 0x80);</span><br><span class="line">  if (value &gt;= (1 &lt;&lt; 7)) &#123;</span><br><span class="line">    target[1] = static_cast&lt;uint8&gt;((value &gt;&gt;  7) | 0x80);</span><br><span class="line">    if (value &gt;= (1 &lt;&lt; 14)) &#123;</span><br><span class="line">      target[2] = static_cast&lt;uint8&gt;((value &gt;&gt; 14) | 0x80);</span><br><span class="line">      if (value &gt;= (1 &lt;&lt; 21)) &#123;</span><br><span class="line">        target[3] = static_cast&lt;uint8&gt;((value &gt;&gt; 21) | 0x80);</span><br><span class="line">        if (value &gt;= (1 &lt;&lt; 28)) &#123;</span><br><span class="line">          target[4] = static_cast&lt;uint8&gt;(value &gt;&gt; 28);</span><br><span class="line">          return target + 5;</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">          target[3] &amp;= 0x7F;</span><br><span class="line">          return target + 4;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        target[2] &amp;= 0x7F;</span><br><span class="line">        return target + 3;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      target[1] &amp;= 0x7F;</span><br><span class="line">      return target + 2;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    target[0] &amp;= 0x7F;</span><br><span class="line">    return target + 1;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>测试了1kw条数据，两种方案的时间对比为 196742us vs 269806us，在pb序列化反序列化大量使用varint的前提下，这个性能提升就很有必要了(这是原作者做的测试)</p>
<p>type 需要注意的是 type = 2 的情况，tag 里面除了包含 field number 和 wire_type ，还需要再包含一个 length，决定 value 从那一段取出来</p>
<h3 id="负数使用varint编码的问题"><a href="#负数使用varint编码的问题" class="headerlink" title="负数使用varint编码的问题"></a>负数使用varint编码的问题</h3><p>varint编码希望以标志位能够节省掉高字节的0，但是负数的最高位一定是1， 所以varint在处理32位负数时会固定的占用5个字节。比如我们修改下之前的程序test.set_a(-1)，序列化之后的数据为</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">08ff ffff ffff ffff ffff 01</span><br></pre></td></tr></table></figure>
<p>有11个字节之多！除了key=0x08占用的1个字节，value=-1占用了10个字节。</p>
<p>对应的代码（coded_stream.h）</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">inline void CodedOutputStream::WriteVarint32SignExtended(int32 value) &#123;</span><br><span class="line">  if (value &lt; 0) &#123;</span><br><span class="line">    WriteVarint64(static_cast&lt;uint64&gt;(value));</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    WriteVarint32(static_cast&lt;uint32&gt;(value));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>int32被转换成了uint64(为什么？)原作者这里问为什么==  原因在文档中有提及</p>
<blockquote>
<p>If you use int32 or int64 as the type for a negative number, the resulting varint is always ten bytes long – it is, effectively, treated like a very large unsigned integer,即uint64，也就是上面代码写的那样。但是为什么生成是10个字节呢? 因为uint64是10个?</p>
</blockquote>
<p>再经过varint编码。这就是10个字节的原因了。当然如果你使用了signed types那么产出的varint编码结果使用了Zigzag编码就会相当的高效。</p>
<h3 id="Zigzag编码"><a href="#Zigzag编码" class="headerlink" title="Zigzag编码"></a>Zigzag编码</h3><p>ZigZag是将有符号数统一映射到无符号数的一种编码方案，对于无符号数0 1 2 3 4，映射前的有符号数分别为0 -1 1 -2 2，负数以及对应的正数来回映射到从0变大的数字序列里，这也是”zig-zag”的名字来源。将所有整数映射成无符号整数，然后再采用 varint 编码方式编码，这样，绝对值小的整数，编码后也会有一个较小的 varint 编码值。</p>
<p>Zigzag 映射函数为：</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">Zigzag(n) = (n &lt;&lt; 1) ^ (n &gt;&gt; 31), n 为 sint32 时</span><br><span class="line">Zigzag(n) = (n &lt;&lt; 1) ^ (n &gt;&gt; 63), n 为 sint64 时</span><br></pre></td></tr></table></figure>
<p>按照这种方法，-1 将会被编码成 1，1 将会被编码成 2，-2 会被编码成 3，如下表所示：</p>
<p><img src="http://or0igopk2.bkt.clouddn.com/18-7-28/17159197.jpg" alt></p>
<p>存疑？</p>
<p>目前仍有一个地方不大清楚，就是对于int32类型的负数，protobuf强制编码成10个字节，理论上5个字节就够了。  （来自别人的问题，我也没懂，确实想了下int32的负数5个就够了，int64的负数才需要10个？）</p>
<h3 id="负数及大整数的解决方案"><a href="#负数及大整数的解决方案" class="headerlink" title="负数及大整数的解决方案"></a>负数及大整数的解决方案</h3><p>protobuf里提供了一种sint32/sint64来使用ZigZag编码。</p>
<p>修改proto:optional sint32 a = 1，这样在test.set_a(-1)并序列化后只有两个字节08 01</p>
<p>同理对于大整数，optional int32 a = 1;，test.set_a(1 &lt;&lt; 28)序列化后可以看到占用了6个字节0880 8080 8001，解决方案也是使用不同的类型定义optional <strong>fixed32</strong> a = 1来解决，使用这种方案后int32固定的占用4个字节。这种其实就是官网中的<code>Non-varint Numbers</code></p>
<h3 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h3><p>wire_type 类型为 2 的数据，是一种指定长度的编码方式：key + length + content，key 的编码方式是统一的，length 采用 varints 编码方式，content 就是由 length 指定长度的 Bytes</p>
<p>举例，假设定义如下的 message 格式：</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></pre></td><td class="code"><pre><span class="line">message Test2 &#123;</span><br><span class="line">  optional string b = 2;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>设置该值为”testing”，二进制格式查看：<code>12 07 74 65 73 74 69 6e 67</code>, <code>74 65 73 74 69 6e 67</code> 是“testing”的 UTF8 代码。</p>
<p>12 -&gt; 0001 0010，后三位 010 为 wire type = 2，0001 0010 右移三位为 0000 0010，即 tag = 2。</p>
<p>length 此处为 7，后边跟着 7 个bytes，即我们的字符串”testing”。</p>
<p>所以 wire_type 类型为 2 的数据，编码的时候会默认转换为 T-L-V (Tag - Length - Value)的形式. TLV的模式减少了分隔符的使用，数据存储更加紧凑。需要转变为 T - L - V 形式的还有 string, bytes, embedded messages, packed repeated fields。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><ul>
<li>Protocol Buffer 利用 varint 原理压缩数据以后，二进制数据非常紧凑，option 也算是压缩体积的一个举措。所以 pb 体积更小，如果选用它作为网络数据传输，势必相同数据，消耗的网络流量更少。但是并没有压缩到极限，float、double 浮点型都没有压缩。</li>
<li>Protocol Buffer 比 JSON 和 XML 少了 {、}、: 这些符号，体积也减少一些。再加上 varint 压缩，gzip 压缩以后体积更小！</li>
<li>Protocol Buffer 是 Tag - Value (Tag - Length - Value)的编码方式的实现，减少了分隔符的使用，数据存储更加紧凑。</li>
<li>Protocol Buffer 另外一个核心价值在于提供了一套工具，一个编译工具，自动化生成 get/set 代码。简化了多语言交互的复杂度，使得编码解码工作有了生产力。</li>
<li>Protocol Buffer 不是自我描述的，离开了数据描述 .proto 文件，就无法理解二进制数据流。这点即是优点，使数据具有一定的“加密性”，也是缺点，数据可读性极差。所以 Protocol Buffer 非常适合内部服务之间 RPC 调用和传递数据。</li>
<li>Protocol Buffer 具有向后兼容的特性，更新数据结构以后，老版本依旧可以兼容，这也是 Protocol Buffer 诞生之初被寄予解决的问题。因为编译器对不识别的新增字段会跳过不处理</li>
</ul>
<h2 id="Protobuf反序列化"><a href="#Protobuf反序列化" class="headerlink" title="Protobuf反序列化"></a>Protobuf反序列化</h2><p><a href="https://halfrost.com/protobuf_encode/" target="_blank" rel="noopener">https://halfrost.com/protobuf_encode/</a></p>
<p>整个解析过程需要 Protobuf 本身的框架代码和由 Protobuf 编译器生成的代码共同完成。Protobuf 提供了基类 Message 以及 Message_lite 作为通用的 Framework，，CodedInputStream 类，WireFormatLite 类等提供了对二进制数据的 decode 功能，从 5.1 节的分析来看，Protobuf 的解码可以通过几个简单的数学运算完成，无需复杂的词法语法分析，因此 ReadTag() 等方法都非常快。 在这个调用路径上的其他类和方法都非常简单，感兴趣的读者可以自行阅读。 相对于 XML 的解析过程，以上的流程图实在是非常简单吧？这也就是 Protobuf 效率高的第二个原因了</p>
<p><img src="http://or0igopk2.bkt.clouddn.com/18-7-28/17159197.jpg" alt></p>
<h2 id="与json-thift的性能比较"><a href="#与json-thift的性能比较" class="headerlink" title="与json thift的性能比较"></a>与json thift的性能比较</h2><p><a href="https://mp.weixin.qq.com/s?__biz=MzA3NDcyMTQyNQ==&amp;mid=2649257430&amp;idx=1&amp;sn=975b6123d8256221f6bac3b99e52af9a&amp;chksm=8767a428b0102d3e6ab7abdf797c481da570cb29e274aa4ff6ecd931f535166b776e6548941d&amp;scene=0&amp;key=399a205ce674169cbedcc1c459650908e22d6a2b81674195c3b251114acdf821dbde7bb49102c6b47f61b26a7a404d74e0e8440cea3675a7ea8f49eafd8639bfb733183a1bfb4603232d6cb8ecd230e5&amp;ascene=0&amp;uin=NTkxMDk2NjU=&amp;devicetype=iMac+MacBookPro12,1+OSX+OSX+10.12.4+build(16E195)&amp;version=12020510&amp;nettype=WIFI&amp;fontScale=100&amp;pass_ticket=wHPj0w18CV8zHl6HCfd9t9LQfs3I0ZULhUILuOHgL0E=" target="_blank" rel="noopener">Protobuf有没有比JSON快5倍？用代码来击破pb性能神话</a></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>protobuf的性能来源于对存储的压缩，避免一切不必要的字节开销。flink中如过将大多数需要存储到state中的对象先转成PB格式会得到很大的性能提升。</p>
<p>和protobuf通常被同时提及的有Apache Thrift / Avro 本文已经没有空间介绍，待后续深入了解。</p>
<p>这篇文章介绍了3中数据结构如何做到对消息体格式演变的透明<br><a href="https://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html" target="_blank" rel="noopener">https://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html</a></p>
<p>参考：</p>
<p><a href="https://developers.google.com/protocol-buffers/docs/javatutorial" target="_blank" rel="noopener">https://developers.google.com/protocol-buffers/docs/javatutorial</a><br><a href="https://developers.google.com/protocol-buffers/docs/encoding#structure" target="_blank" rel="noopener">https://developers.google.com/protocol-buffers/docs/encoding#structure</a><br><a href="https://halfrost.com/protobuf_encode/" target="_blank" rel="noopener">https://halfrost.com/protobuf_encode/</a><br><a href="https://izualzhy.cn/protobuf-encode-varint-and-zigzag" target="_blank" rel="noopener">https://izualzhy.cn/protobuf-encode-varint-and-zigzag</a><br><a href="https://izualzhy.cn/protobuf-encoding" target="_blank" rel="noopener">https://izualzhy.cn/protobuf-encoding</a><br><a href="https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html" target="_blank" rel="noopener">https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html</a><br><a href="https://segmentfault.com/a/1190000004891020" target="_blank" rel="noopener">https://segmentfault.com/a/1190000004891020</a>  protobufstuff</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文带你深入理解和使用protobuf&lt;/p&gt;
    
    </summary>
    
      <category term="刨根问底" scheme="http://www.aitozi.com/categories/%E5%88%A8%E6%A0%B9%E9%97%AE%E5%BA%95/"/>
    
    
      <category term="protobuf" scheme="http://www.aitozi.com/tags/protobuf/"/>
    
  </entry>
  
</feed>
