<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Sixzeroo</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://www.liuin.cn/"/>
  <updated>2019-11-03T13:24:47.000Z</updated>
  <id>https://www.liuin.cn/</id>
  
  <author>
    <name>Sixzeroo</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Go语言并发map</title>
    <link href="https://www.liuin.cn/2019/06/13/Go%E8%AF%AD%E8%A8%80%E5%B9%B6%E5%8F%91map/"/>
    <id>https://www.liuin.cn/2019/06/13/Go语言并发map/</id>
    <published>2019-06-13T13:59:37.000Z</published>
    <updated>2019-11-03T13:24:47.000Z</updated>
    
    <content type="html"><![CDATA[<p>Go 语言的map在高并发场景下会Panic，本文分析了三种在高并发场景下使用map的解决方案。</p><a id="more"></a><p>Map是各个语言中都有一种数据结构，在原生支持并发的Go这里也例外。但是在高并发场景下使用原生的Map恰恰是存在一些坑的。</p><h2 id="原生map高并发问题"><a href="#原生map高并发问题" class="headerlink" title="原生map高并发问题"></a>原生map高并发问题</h2><p><a href="https://blog.golang.org/go-maps-in-action" target="_blank" rel="noopener">Go官方博客</a>中对有一些说明：</p><blockquote><p>Maps are not safe for concurrent use: it’s not defined what happens when you read and write to them simultaneously. If you need to read from and write to a map from concurrently executing goroutines, the accesses must be mediated by some kind of synchronization mechanism. One common way to protect maps is with sync.RWMutex.</p></blockquote><p>意思是说，并发访问map是不安全的，并发读写时会出现未定义行为。如果希望多协程访问读写map，必须提供某种同步机制，最常用的是<code>sync.RWMutex</code>。</p><h2 id="直接加锁"><a href="#直接加锁" class="headerlink" title="直接加锁"></a>直接加锁</h2><p>使用<code>sync.RWMutex</code>加锁是最直接的一种方式，但是存在两个问题：</p><ol><li>使用起来并不方便，需要我们自己封装一层读写逻辑</li><li>性能比较差，写一个key会锁住整个map</li></ol><p>示例：<br><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></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">"sync"</span></span><br><span class="line"><span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> CycleNum = <span class="number">100000</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Map <span class="keyword">interface</span> &#123;</span><br><span class="line">Set(key, value <span class="keyword">string</span>)</span><br><span class="line">Get(key <span class="keyword">string</span>) (<span class="keyword">string</span>, <span class="keyword">bool</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> MutexMap <span class="keyword">struct</span> &#123;</span><br><span class="line">m sync.RWMutex</span><br><span class="line">c <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</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">NewMutexMap</span><span class="params">()</span> *<span class="title">MutexMap</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;MutexMap&#123;</span><br><span class="line">c: <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>),</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *MutexMap)</span> <span class="title">Set</span><span class="params">(key, value <span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line">m.m.Lock()</span><br><span class="line"><span class="keyword">defer</span> m.m.Unlock()</span><br><span class="line">m.c[key] = value</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *MutexMap)</span> <span class="title">Get</span><span class="params">(key <span class="keyword">string</span>)</span> <span class="params">(<span class="keyword">string</span>, <span class="keyword">bool</span>)</span></span> &#123;</span><br><span class="line">m.m.RLock()</span><br><span class="line"><span class="keyword">defer</span> m.m.RUnlock()</span><br><span class="line">res, ok := m.c[key]</span><br><span class="line"><span class="keyword">return</span> res, ok</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">beginTime := time.Now()</span><br><span class="line"></span><br><span class="line">mutexMap := NewMutexMap()</span><br><span class="line">wg := sync.WaitGroup&#123;&#125;</span><br><span class="line">wg.Add(CycleNum)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; CycleNum; i++ &#123;</span><br><span class="line">s := fmt.Sprintf(<span class="string">"%d"</span>, i)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">mutexMap.Set(s, s)</span><br><span class="line">&#125;()</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">mutexMap.Get(s)</span><br><span class="line">&#125;()</span><br><span class="line">wg.Done()</span><br><span class="line">&#125;</span><br><span class="line">wg.Wait()</span><br><span class="line"></span><br><span class="line">spanTime := time.Now().Sub(beginTime)</span><br><span class="line">fmt.Printf(<span class="string">"spend time: %v"</span>, spanTime)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="sync-Map"><a href="#sync-Map" class="headerlink" title="sync.Map"></a>sync.Map</h2><p><code>sync.Map</code> 是Go 1.9 以后标准库中提供的并发Map。其内部实现是引入两个map将读写进行分离。</p><p>其中read map只提供读，而dirty map则负责写。这样read map就可以在不加锁的情况下进行并发读取，当read map中没有读取到值时，再加锁进行后续读取，并累加未命中数，当未命中数到达dirty map的长度时，用dirty map替换read map。虽然引入了两个map，但是底层数据存储的是指针，指向的是同一份值</p><p>源码：</p><p>数据结构<br><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Map <span class="keyword">struct</span> &#123;</span><br><span class="line">  <span class="comment">// 互斥锁</span></span><br><span class="line">mu Mutex</span><br><span class="line">  <span class="comment">// 读map</span></span><br><span class="line">read atomic.Value <span class="comment">// readOnly</span></span><br><span class="line"><span class="comment">// 写map</span></span><br><span class="line">dirty <span class="keyword">map</span>[<span class="keyword">interface</span>&#123;&#125;]*entry</span><br><span class="line">misses <span class="keyword">int</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>Load方法：<br><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Map)</span> <span class="title">Load</span><span class="params">(key <span class="keyword">interface</span>&#123;&#125;)</span> <span class="params">(value <span class="keyword">interface</span>&#123;&#125;, ok <span class="keyword">bool</span>)</span></span> &#123;</span><br><span class="line">read, _ := m.read.Load().(readOnly)</span><br><span class="line">e, ok := read.m[key]</span><br><span class="line"><span class="keyword">if</span> !ok &amp;&amp; read.amended &#123;</span><br><span class="line">m.mu.Lock()</span><br><span class="line"><span class="comment">// 避免在我们加锁的时候，key写入了读map中</span></span><br><span class="line"><span class="comment">// amended 标记dirty map中含有read map中不存在的数据</span></span><br><span class="line">read, _ = m.read.Load().(readOnly)</span><br><span class="line">e, ok = read.m[key]</span><br><span class="line"><span class="keyword">if</span> !ok &amp;&amp; read.amended &#123;</span><br><span class="line">e, ok = m.dirty[key]</span><br><span class="line"><span class="comment">// 记录miss数，到达dirty长度的时候进行替换</span></span><br><span class="line">m.missLocked()</span><br><span class="line">&#125;</span><br><span class="line">m.mu.Unlock()</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> !ok &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> e.load()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>Store方法：<br><figure class="highlight golang"><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">func</span> <span class="params">(m *Map)</span> <span class="title">Store</span><span class="params">(key, value <span class="keyword">interface</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line">read, _ := m.read.Load().(readOnly)</span><br><span class="line"><span class="keyword">if</span> e, ok := read.m[key]; ok &amp;&amp; e.tryStore(&amp;value) &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">  <span class="comment">// 没有在read map中读取到值，或者读取到值但是更新失败</span></span><br><span class="line">m.mu.Lock()</span><br><span class="line">read, _ = m.read.Load().(readOnly)</span><br><span class="line"><span class="keyword">if</span> e, ok := read.m[key]; ok &#123;</span><br><span class="line"><span class="keyword">if</span> e.unexpungeLocked() &#123;</span><br><span class="line"><span class="comment">// 读到的值为删除状态，写入dirty map中</span></span><br><span class="line">m.dirty[key] = e</span><br><span class="line">&#125;</span><br><span class="line">e.storeLocked(&amp;value)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> e, ok := m.dirty[key]; ok &#123;</span><br><span class="line">  <span class="comment">// dirty map中有这个值，进行更新</span></span><br><span class="line">e.storeLocked(&amp;value)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">//</span></span><br><span class="line"><span class="keyword">if</span> !read.amended &#123;</span><br><span class="line">m.dirtyLocked()</span><br><span class="line">m.read.Store(readOnly&#123;m: read.m, amended: <span class="literal">true</span>&#125;)</span><br><span class="line">&#125;</span><br><span class="line">m.dirty[key] = newEntry(value)</span><br><span class="line">&#125;</span><br><span class="line">m.mu.Unlock()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="分段锁"><a href="#分段锁" class="headerlink" title="分段锁"></a>分段锁</h2><h2 id="各场景下Benchmark"><a href="#各场景下Benchmark" class="headerlink" title="各场景下Benchmark"></a>各场景下Benchmark</h2><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Go 语言的map在高并发场景下会Panic，本文分析了三种在高并发场景下使用map的解决方案。&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="Go" scheme="https://www.liuin.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Kafka 面试题整理</title>
    <link href="https://www.liuin.cn/2019/05/08/Kafka-%E9%9D%A2%E8%AF%95%E9%A2%98%E6%95%B4%E7%90%86/"/>
    <id>https://www.liuin.cn/2019/05/08/Kafka-面试题整理/</id>
    <published>2019-05-08T14:43:19.000Z</published>
    <updated>2019-10-22T13:09:05.000Z</updated>
    
    <content type="html"><![CDATA[<p>本篇博客是阅读完《深入理解Kafka:核心设计与实践原理》后对作者博客中提到的<a href="https://blog.csdn.net/u013256816/article/details/88550812" target="_blank" rel="noopener">面试题</a>整理的解答</p><a id="more"></a><h1 id="已解答"><a href="#已解答" class="headerlink" title="已解答"></a>已解答</h1><ol><li>Kafka的用途有哪些？使用场景如何？<blockquote><p>Kafka是一个分布式的消息系统，作为消息系统，他具备系统解耦，冗余存储，流量削峰，缓冲、异步通信等功能。同时他还是一个存储系统和流式处理平台，作为存储系统他能够把消息持久化到磁盘，降低数据丢失的风险；作为流式处理平台，不仅能够为各个流式处理框架提供稳定的数据来源，还提供了一些流式处理的库</p></blockquote></li></ol><ol start="2"><li><p>Kafka中的ISR、AR又代表什么？ISR的伸缩又指什么？</p><blockquote><p>分区中的所有副本统称为AR(Assigned Replicas)。所有与 leader副本保持一定程度同步的副本(包括 leader副本在内)组成ISR(In-Sync Replicas)，ISR集合是AR集合中的一个子集。消息会先发送到 leader副本，然后 follower副本才能从 leader副本中拉取消息进行同步，同步期间内 follower副本相对于 leader副本而言会有一定程度的滞后。前面所说的“一定程度的同步”是指可忍受的滞后范围，这个范围可以通过参数进行配置。与 leader副本同步滞后过多的副本(不包括 leader副本)组成OSR(Out-of-Sync Replicas)，由此可见，<strong>AR=ISR+OSR</strong>。在正常情况下，所有的 follower副本都应该与 leader副本保持一定程度的同步，即AR=ISR，OSR集合为空。<br>ISR的伸缩是指leader副本负责跟踪ISR集合中所有follower副本的滞后状态，有follower副本滞后太多的时候将他从ISR中剔除，OSR集合中有follower副本”追上“了leader副本将其加入ISR集合中</p></blockquote></li><li><p>Kafka中的HW、LEO、LSO、LW等分别代表什么？</p><blockquote><p>HW是 High Watermark的缩写，俗称高水位，它标识了一个特定的消息偏移量(offset)，消费者只能拉取到这个offset之前的消息。<br>LEO是 Log End Offset的缩写，它标识当前日志文件中下一条待写入消息的 offset。LEO的大小相当于当前日志分区中最后一条消息的 offset值加1。分区ISR集合中的每个副本都会维护自身的LEO，而ISR集合中最小的LEO即为分区的HW，对消费者而言只能消费HW之前的消息。<br>LSO(Last Stable Offset) 对未完成的事务而言，LSO 的值等于事务中第一条消息的位置(firstUnstableOffset)，对已完成的事务而言，它的值同 HW 相同<br>LW:Low Watermark 低水位, 代表 AR 集合中最小的 logStartOffset 值</p></blockquote></li><li><p>Kafka中是怎么体现消息顺序性的？</p><blockquote><p>通过Topic和Partition来提现，Topic是消息归类的主题是逻辑概念，Topic下有多个Partition，其在存储层面可以看成是一个可追加的日志文件，消息在被追加到Partition日志文件中的时候会分配一个特定的偏移量（offset）。offset是消息在分区中的唯一标识，Kafka通过offset来保证消息在分区内的顺序性</p></blockquote></li><li><p>Kafka中的分区器、序列化器、拦截器是否了解？它们之间的处理顺序是什么？</p><blockquote><p>分区器、序列化器、拦截器都是生产者客户端中的东西。<br>分区器是将消息发送给指定的分区的，如果在发送的消息中指定了Partition，就不需要分区器了，默认的分区器中对key进行哈希（采用MurmurHash2算法，具备高运算性能及低碰撞率），同时也可以自定义分区器<br>序列化器把消息对象转换成字节数组，这样才能够通过网络发送给Kafka。<br>生产者拦截器既可以用来在消息发送前做一些准备工作,比如按照某个规则过滤不符合要求的消息、修改消息的内容等,也可以用来在发送回调逻辑前做一些定制化的需求,比如统计类工作。<br>他们之间工作的顺序是 拦截器 -&gt; 序列化器 -&gt; 分区器</p></blockquote></li><li><p>Kafka生产者客户端的整体结构是什么样子的？</p><blockquote><p><img src="http://data3.liuin.cn/2019/05/09/Kafka 生产者客户端架构.png" alt="Kafka 生产者客户端架构"></p></blockquote></li><li><p>Kafka生产者客户端中使用了几个线程来处理？分别是什么？</p><blockquote><p>使用了两个线程来进行处理：主线程和Sender线程。<br>主线程负责由KafkaProducer创建消息，通过拦截器、序列化器和分区器作用以后缓存到消息累加器；Sender线程负责从消息累加器中获取消息并将其发送到Kafka中</p></blockquote></li><li><p>“消费组中的消费者个数如果超过topic的分区，那么就会有消费者消费不到数据”这句话是否正确？如果正确，那么有没有什么hack的手段？</p><blockquote><p>是正确的，可以让多个消费线程消费同一个分区来hack，通过assign()、seek()等方法实现，这样可以打破原有的消费线程的个数不能超过分区的限制。</p></blockquote></li><li><p>消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1?</p><blockquote><p>提交的是offset + 1，表示下一条需要拉取的消息的位置</p></blockquote></li><li><p>有哪些情形会造成重复消费？</p><blockquote><p>位移提交动作在消费完所有拉取到的消息后才执行会造成重复消费，因为批量拉取一批消息以后，中间消费处理的过程中可能会出现异常，这样的会前面消费的消息就会再消费一次<br>Rebalance 的时候也会出现，一个分区在原有消费者的位移没有上传的时候分配给另外一个消费者，另外一个消费会从上一次位移的地方继续拉取消息进行消费，这样就造成了消息的重复消费，后续可以通过添加Rebalance的监听器来做一些Rebalance的工作来解决位移未提交的问题</p></blockquote></li><li><p>那些情景下会造成消息漏消费？</p><blockquote><p>位移提交动作在消费消息之前会造成消息漏消费，因为提交新的位移以后，可能拉取到的那一批消息中间出现异常，那么那一批消息后面的那一部分消息就不会被消费到，这样就导致了消息的漏消费</p></blockquote></li><li><p>KafkaConsumer是非线程安全的，那么怎么样实现多线程消费？</p><blockquote><p>第一种方式：线程封闭，每个线程实例化一个KafkaConsumer对象，一个消费线程可以消费一个或者多个分区中的消息，所有的消费线程都隶属于同一个消费者组。<br>第二种方式：多个消费线程同时消费同一个分区，通过assign()、seek()等方法实现，这样可以打破原有的消费线程的个数不能超过分区的限制，进一步提高消费能力。但是这种方式会导致位移提交和顺序控制的处理变得更加复杂。</p></blockquote></li><li><p>简述消费者与消费组之间的关系</p><blockquote><p>每一个消费者都属于一个消费者组中，消费者组能够消费到一个主题中的所有消息（实现多播），然后把这些消息通过Partition负载均衡分配给具体的消费者进行消费。<br><img src="http://data3.liuin.cn/2019/05/09/Kafka Consumer Group.png" alt="Kafka Consumer Group"></p></blockquote></li></ol><h1 id="未解答"><a href="#未解答" class="headerlink" title="未解答"></a>未解答</h1><ol><li><del>Kafka的用途有哪些？使用场景如何？</del></li><li><del>Kafka中的ISR、AR又代表什么？ISR的伸缩又指什么</del></li><li><del>Kafka中的HW、LEO、LSO、LW等分别代表什么？</del></li><li><del>Kafka中是怎么体现消息顺序性的？</del></li><li><del>Kafka中的分区器、序列化器、拦截器是否了解？它们之间的处理顺序是什么？</del></li><li><del>Kafka生产者客户端的整体结构是什么样子的？</del></li><li><del>Kafka生产者客户端中使用了几个线程来处理？分别是什么？</del></li><li>Kafka的旧版Scala的消费者客户端的设计有什么缺陷？</li><li><del>“消费组中的消费者个数如果超过topic的分区，那么就会有消费者消费不到数据”这句话是否正确？如果正确，那么有没有什么hack的手段？</del></li><li><del>消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1?</del></li><li><del>有哪些情形会造成重复消费？</del></li><li><del>那些情景下会造成消息漏消费？</del></li><li><del>KafkaConsumer是非线程安全的，那么怎么样实现多线程消费？</del></li><li><del>简述消费者与消费组之间的关系</del></li><li>当你使用kafka-topics.sh创建（删除）了一个topic之后，Kafka背后会执行什么逻辑？</li><li>topic的分区数可不可以增加？如果可以怎么增加？如果不可以，那又是为什么？</li><li>topic的分区数可不可以减少？如果可以怎么减少？如果不可以，那又是为什么？</li><li>创建topic时如何选择合适的分区数？</li><li>Kafka目前有那些内部topic，它们都有什么特征？各自的作用又是什么？</li><li>优先副本是什么？它有什么特殊的作用？</li><li>Kafka有哪几处地方有分区分配的概念？简述大致的过程及原理</li><li>简述Kafka的日志目录结构</li><li>Kafka中有那些索引文件？</li><li>如果我指定了一个offset，Kafka怎么查找到对应的消息？</li><li>如果我指定了一个timestamp，Kafka怎么查找到对应的消息？</li><li>聊一聊你对Kafka的Log Retention的理解</li><li>聊一聊你对Kafka的Log Compaction的理解</li><li>聊一聊你对Kafka底层存储的理解（页缓存、内核层、块层、设备层）</li><li>聊一聊Kafka的延时操作的原理</li><li>聊一聊Kafka控制器的作用</li><li>消费再均衡的原理是什么？（提示：消费者协调器和消费组协调器）</li><li>Kafka中的幂等是怎么实现的</li><li>Kafka中的事务是怎么实现的（这题我去面试6加被问4次，照着答案念也要念十几分钟，面试官简直凑不要脸）</li><li>Kafka中有那些地方需要选举？这些地方的选举策略又有哪些？</li><li>失效副本是指什么？有那些应对措施？</li><li>多副本下，各个副本中的HW和LEO的演变过程</li><li>为什么Kafka不支持读写分离？</li><li>Kafka在可靠性方面做了哪些改进？（HW， LeaderEpoch）</li><li>Kafka中怎么实现死信队列和重试队列？</li><li>Kafka中的延迟队列怎么实现（这题被问的比事务那题还要多！！！听说你会Kafka，那你说说延迟队列怎么实现？）</li><li>Kafka中怎么做消息审计？</li><li>Kafka中怎么做消息轨迹？</li><li>Kafka中有那些配置参数比较有意思？聊一聊你的看法</li><li>Kafka中有那些命名比较有意思？聊一聊你的看法</li><li>Kafka有哪些指标需要着重关注？</li><li>怎么计算Lag？(注意read_uncommitted和read_committed状态下的不同)</li><li>Kafka的那些设计让它有如此高的性能？</li><li>Kafka有什么优缺点？</li><li>还用过什么同质类的其它产品，与Kafka相比有什么优缺点？</li><li>为什么选择Kafka?</li><li>在使用Kafka的过程中遇到过什么困难？怎么解决的？</li><li>怎么样才能确保Kafka极大程度上的可靠性？</li><li>聊一聊你对Kafka生态的理解</li></ol>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本篇博客是阅读完《深入理解Kafka:核心设计与实践原理》后对作者博客中提到的&lt;a href=&quot;https://blog.csdn.net/u013256816/article/details/88550812&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;面试题&lt;/a&gt;整理的解答&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="Kafka" scheme="https://www.liuin.cn/tags/Kafka/"/>
    
  </entry>
  
  <entry>
    <title>Redis Cluster集群与集群伸缩</title>
    <link href="https://www.liuin.cn/2018/09/06/Redis-Cluster%E9%9B%86%E7%BE%A4%E4%B8%8E%E9%9B%86%E7%BE%A4%E4%BC%B8%E7%BC%A9/"/>
    <id>https://www.liuin.cn/2018/09/06/Redis-Cluster集群与集群伸缩/</id>
    <published>2018-09-06T19:24:27.000Z</published>
    <updated>2019-01-06T14:12:57.000Z</updated>
    
    <content type="html"><![CDATA[<p>使用Redis的时候遇到单机内存、并发、流量等瓶颈时，可以采用Cluster架构达到负载均衡的目的。Redis Cluster是Redis的分布式解决方案。</p><a id="more"></a><h2 id="Redis-Cluster介绍"><a href="#Redis-Cluster介绍" class="headerlink" title="Redis Cluster介绍"></a>Redis Cluster介绍</h2><p>Redis Cluster 作为一个分布式的解决方案，首先要解决的就是把整个数据集按照分区规则映射到多个节点的问题，即把数据集划分到多个节点上，每个节点负责整体数据的一个子集。</p><h3 id="常见哈希分区规则"><a href="#常见哈希分区规则" class="headerlink" title="常见哈希分区规则"></a>常见哈希分区规则</h3><p>常见的分区规则有哈希分区和顺序分区，这两种分区各有各的特点，Redis采用的哈希分区的方法。</p><p><img src="http://data3.liuin.cn/2018-10-01-15383778091095.jpg" alt=""></p><p>对应哈希分区常见有如下几种规则</p><h4 id="节点取余分区"><a href="#节点取余分区" class="headerlink" title="节点取余分区"></a>节点取余分区</h4><p>将key哈希之后对节点数量取余，决定数据最终的映射点。</p><p>优点：</p><ul><li>简单，常用于数据库的分库分表规则</li></ul><p>问题：</p><ul><li>当节点数量变化时，这个映射关系需要重新计算</li></ul><h4 id="一致性哈希分区"><a href="#一致性哈希分区" class="headerlink" title="一致性哈希分区"></a>一致性哈希分区</h4><p>为系统中的每一个节点赋一个token值，这些token构成一个环。读写key的时候，计算key的hash值，然后再环中顺时针寻找离这个key最近的节点。</p><p><img src="http://data3.liuin.cn/2018-10-01-15383781014879.jpg" alt=""></p><p>优点：</p><ul><li>加入和删除节点只影响哈希环中 相邻的节点，对其他节点无影响</li></ul><p>问题：</p><ul><li>加减节点会导致部分数据无法命中，需要单独处理</li><li>要保证整体的负载均衡，在加减节点视要增加一倍或者减少一半</li></ul><h4 id="虚拟槽分区"><a href="#虚拟槽分区" class="headerlink" title="虚拟槽分区"></a>虚拟槽分区</h4><p>Redis Clust采用的就是虚拟槽分区的规则，首先用一个分散度良好的哈希函数把所有的数据映射到一个固定范围的整数集中，整数定义为槽（slot）。槽是集群内数据 管理和迁移的基本单位。每个节点负责处理指定数量的槽的数据。</p><p>Redis Clust采用的哈希函数是CRC16方法，总共分了16383个槽。</p><p>优点：</p><ul><li>解耦数据和节点之间的关系，简化了节点扩容和收缩难度</li><li>节点自身维护槽的映射关系</li></ul><h3 id="集群功能局限"><a href="#集群功能局限" class="headerlink" title="集群功能局限"></a>集群功能局限</h3><p>Redis Clust集群相比单机Redis功能上存在一些限制：</p><ul><li>key批量操作支持有限。如mset、mget，目前只支持具有相同slot值的 key执行批量操作。</li><li>key事务操作支持有限。同理只支持多key在同一节点上的事务操 作，当多个key分布在不同的节点上时无法使用事务功能。</li><li>key作为数据分区的最小粒度，因此不能将一个大的键值对象如 hash、list等映射到不同的节点</li><li>不支持多数据库空间，集群模式下只能够使用数据库0</li></ul><h2 id="Redis-Cluster搭建"><a href="#Redis-Cluster搭建" class="headerlink" title="Redis Cluster搭建"></a>Redis Cluster搭建</h2><h3 id="节点准备"><a href="#节点准备" class="headerlink" title="节点准备"></a>节点准备</h3><p>Redis集群一般由多个节点组成，节点数量至少为6个才能保证组成完整高可用的集群。建议为集群内所有节点<strong>统一目录</strong>，一般划分三个目录：conf、 data、log，分别存放配置、数据和日志相关文件。</p><p>相比单机模式需要改变的配置如下：<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></pre></td><td class="code"><pre><span class="line">port 6379                               //端口</span><br><span class="line">cluster-enabled yes                     //开启集群模式</span><br><span class="line">cluster-config-file nodes-6379.conf     //集群内部的配置文件</span><br><span class="line">cluster-node-timeout 15000              //节点超时时间，单位毫秒</span><br></pre></td></tr></table></figure></p><p>注意到上面配置了集群内部的配置文件，集群配置文件的作用：当集群内节点发生信息变化时，如添加节点、节点下线、故障转移等。节点会自动保存集群的状态到配置文件中。该配置文件由Redis自行维护，不要手动修改，防止节点重启时产生集群信息错乱。</p><p>启动所有节点以后可以通过 <code>CLUSTER NODES</code> 查看集群状态</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">127.0.0.1:6379&gt; CLUSTER NODES</span><br><span class="line">29978c0169ecc0a9054de7f4142155c1ab70258b :6379 myself,master - 0 0 0 connected</span><br></pre></td></tr></table></figure><h3 id="节点握手"><a href="#节点握手" class="headerlink" title="节点握手"></a>节点握手</h3><p>节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信， 达到感知对方的过程。节点握手是集群彼此通信的第一步，由客户端发起命令：<code>CLUSTER MEET [ip] [port]</code></p><p>Redis Clust的节点中是用<code>clusterNode</code>表示一个节点的状态的，握手实际上就是两个节点交换<code>clusterNode</code>的过程，交换的不仅仅是自己的<code>clusterNode</code>还有之间已经通过握手获取的<code>clusterNode</code>信息。当集群中所有的节点都互通以后，每一个节点都会拥有整个集群所有节点的<code>clusterNode</code>信息。</p><h3 id="分配槽"><a href="#分配槽" class="headerlink" title="分配槽"></a>分配槽</h3><p>Redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固 定的槽，只有当节点分配了槽，才能响应和这些槽关联的键命令。通过 <code>CLUSTER ADDSLOTS</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></pre></td><td class="code"><pre><span class="line">redis-cli -h 127.0.0.1 -p 6379 cluster addslots &#123;0..5461&#125;</span><br><span class="line">OK</span><br><span class="line">redis-cli -h 127.0.0.1 -p 6380 cluster addslots &#123;5462..10922&#125;</span><br><span class="line">OK</span><br><span class="line">redis-cli -h 127.0.0.1 -p 6381 cluster addslots &#123;10923..16383&#125;</span><br><span class="line">OK</span><br></pre></td></tr></table></figure><p>当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)，可以通过<code>CLUSTER INFO</code>获取到的集群状态查看</p><h2 id="Redis-Cluster集群伸缩"><a href="#Redis-Cluster集群伸缩" class="headerlink" title="Redis Cluster集群伸缩"></a>Redis Cluster集群伸缩</h2><p>Redis集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务 的情况下，可以为集群添加节点进行扩容也可以下线部分节点进行缩容。</p><p>伸缩的重点是槽位的重新分配，和数据的转移。</p><h3 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h3><p>Redis集群的伸缩操作是由Redis的集群管理软件<code>redis-trib</code>负责执行的,Redis提供了进行重新分片所需的所有命令,而<code>redis-trib</code>则通过向源节点和目标节点发送命令来进行重新分片操作。</p><p><code>redis-trib</code>对集群的单个槽s1ot进行重新分片的步骤如下</p><ol><li><code>redis-trib</code>对目标节点发送 <code>CLUSTER SETSLOT&lt;sot&gt; IMPORTING&lt;source_id&gt;</code>命令,让目标节点准备好从源节点导人(Import)属于槽s1ot的键值对。</li><li><code>redis-trib</code>对源节点发送 <code>CLUSTER SETSLOT&lt;s1ot&gt; MIGRATING&lt;target_id&gt;</code>命令,让源节点准备好将属于槽s1ot的键值对迁移(migrate)至目标节点。</li><li><code>redis-trib</code>向源节点发送 <code>CLUSTER GETKEYSINSLOT&lt;s1ot&gt; &lt;count&gt;</code>命令,获得最多count个属于槽s1ot的键值对的键名(key name)</li><li>对于步骤3获得的每个键名,<code>redis-trib</code>都向源节点发送一个 <code>MIGRATE &lt;target_ip&gt; &lt;target_port&gt; &lt;key_name&gt; &lt;timeout&gt;</code>命令,将被选中的键原子地从源节点迁移至目标节点。</li><li>重复执行步骤3和步骤4,直到源节点保存的所有属于槽s1ot的键值对都被迁移至目标节点为止。</li><li><code>redis-trib</code>向集群中的任意一个节点发送 `CLUSTER SETSLOT <slot> NODE &lt;target_id&gt; 命令,将槽slot指派绐目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽s1ot已经指派给了目标节点。</slot></li></ol><p><img src="http://data3.liuin.cn/2018-10-01-15383800895858.jpg" alt=""></p><h3 id="ASK-错误"><a href="#ASK-错误" class="headerlink" title="ASK 错误"></a>ASK 错误</h3><p>在进行集群伸缩期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。</p><p>当客户端向源节点发送个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:</p><ul><li>源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。</li><li>相反地,如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令。</li></ul><h3 id="操作"><a href="#操作" class="headerlink" title="操作"></a>操作</h3><p>扩容：</p><ul><li>首先要让扩容的Redis节点上线并完成握手的过程</li><li>对槽分布和里面的数据进行迁移</li></ul><p>缩容：</p><ul><li>对槽分布和里面的数据进行迁移，确保缩容节点没有分配槽</li><li>对缩容节点进行忘记处理，命令：<code>CLUST FORGET [NodeId]</code></li></ul><p>参考：</p><ul><li>《Redis 开发与运维》</li><li>《Redis 设计与实现》</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;使用Redis的时候遇到单机内存、并发、流量等瓶颈时，可以采用Cluster架构达到负载均衡的目的。Redis Cluster是Redis的分布式解决方案。&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="Redis" scheme="https://www.liuin.cn/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>算法总结——第K大相关问题总结</title>
    <link href="https://www.liuin.cn/2018/08/29/%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93%E2%80%94%E2%80%94%E7%AC%ACK%E5%A4%A7%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/"/>
    <id>https://www.liuin.cn/2018/08/29/算法总结——第K大相关问题总结/</id>
    <published>2018-08-29T21:51:31.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>总结第K大相关问题</p><a id="more"></a><h2 id="第K大数"><a href="#第K大数" class="headerlink" title="第K大数"></a>第K大数</h2><p>题意非常简单，给出一堆乱序的数，让你找出这堆数中第k大的数</p><h3 id="排序法"><a href="#排序法" class="headerlink" title="排序法"></a>排序法</h3><p>直接对这一堆乱序的数进行排序，然后取出第k大的数。这样做的时间复杂度是O(nlogn)</p><h3 id="建堆法"><a href="#建堆法" class="headerlink" title="建堆法"></a>建堆法</h3><p>首先对这些数建一个最大堆，然后再对最大堆堆顶的数pop k次，因为建堆的时间复杂度是O（4n）,所以整体的时间复杂度也是O(4n + klogn)</p><h3 id="快排法"><a href="#快排法" class="headerlink" title="快排法"></a>快排法</h3><p>利用快排算法的思想进行变形，每次在数组中随机找一个拆分数，类似快排的思想把这个拆分数放到数组中应该存在的位置，判断拆分数排列第几，如果在k的后面表示第k个数需要在前半部分找；如果在k的前面表示需要后面半部分找。从而使规模一步一步缩小。</p><p>最后可以算出时间复杂度期望为O(n)，因为这取决于你拆分数的选取。</p><h3 id="BFPRT算法"><a href="#BFPRT算法" class="headerlink" title="BFPRT算法"></a>BFPRT算法</h3><p>前面说了快排法能够使得其时间复杂度到达期望为O(n)，那能不能使得时间复杂度严格为O(n)呢？可以的，这里就引出了BFPRT算法。</p><p>首先我们可以来看一下BFPRT算法的步骤：</p><ol><li>分组（5个一组）</li><li>小组内排序(对于每一组的排序的时间复杂度是O(1))</li><li>把每个组的中位数拿出来组成一个新的数组</li><li>使用BFPRT递归算出这个中位数组的中位数</li><li>用这个当做划分值进行划分</li><li>划分成两边以后递归调用直到找到第k个数</li></ol><p>原理：<br>通过这样选到的划分数一定在0.3N-0.7N之间，如图所示</p><p><img src="http://data3.liuin.cn/2018-09-30-15383135225801.jpg" alt=""></p><p>每次选取到的划分数一定都比红色的数要大，比蓝色的数要小</p><p>这样时间复杂度的迭代公式就是：</p><p><img src="http://data3.liuin.cn/2018-09-30-15383136239459.jpg" alt=""></p><p>通过推导不难算出其时间复杂度严格为O(n)</p><h2 id="前K大数"><a href="#前K大数" class="headerlink" title="前K大数"></a>前K大数</h2><p>求前K大的数就无法做到时间复杂度为O(n)了，但是方法是相似的</p><h3 id="最小堆法"><a href="#最小堆法" class="headerlink" title="最小堆法"></a>最小堆法</h3><ol><li>首先构造一个大小为k的最小堆</li><li>从k+1个数开始，当比堆顶大，加入堆中并维持堆大小为k（维持可以通过限定索引做到）</li><li>这样遍历了所有的数以后，堆中的这k个数就是前k个数了</li></ol><h3 id="快排法-1"><a href="#快排法-1" class="headerlink" title="快排法"></a>快排法</h3><p>依然可以用快排法</p><p>只不过在找到第k大的数之后需要对后面比这个数大的数进行一遍排序</p><p>参考：</p><ul><li><a href="https://blog.csdn.net/jeffleo/article/details/64133292" target="_blank" rel="noopener">https://blog.csdn.net/jeffleo/article/details/64133292</a></li><li><a href="https://segmentfault.com/a/1190000009645951#articleHeader7" target="_blank" rel="noopener">https://segmentfault.com/a/1190000009645951#articleHeader7</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;总结第K大相关问题&lt;/p&gt;
    
    </summary>
    
      <category term="算法" scheme="https://www.liuin.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法总结" scheme="https://www.liuin.cn/tags/%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>Go 易采坑总结</title>
    <link href="https://www.liuin.cn/2018/08/23/Go-%E6%98%93%E9%87%87%E5%9D%91%E6%80%BB%E7%BB%93/"/>
    <id>https://www.liuin.cn/2018/08/23/Go-易采坑总结/</id>
    <published>2018-08-23T09:14:34.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>汇总一些本人积累的Go语言中容易犯的错误（持续更新）</p><a id="more"></a><h2 id="交锋过的"><a href="#交锋过的" class="headerlink" title="交锋过的"></a>交锋过的</h2><h3 id="nil的比较"><a href="#nil的比较" class="headerlink" title="nil的比较"></a>nil的比较</h3><p>在Go语言中，一个interface{}类型的变量包含两个指针，一个指向其类型，另一个指向真正的值。所以对于一个interface{}类型的nil变量来说，它的两个指针都是0。这是符合Go语言对nil的标准定义的。</p><p>当我们将一个具体类型的值赋值给一个interface类型的变量的时候，就同时把类型和值都赋值给了interface里的两个指针。如果这个具体类型的值是nil的话，interface变量依然会存储对应的类型指针和值指针。这个时候拿这个interface变量去和nil常量进行比较的话就会返回<code>false</code></p><figure class="highlight golang"><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="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> p *<span class="keyword">int</span> = <span class="literal">nil</span></span><br><span class="line"><span class="keyword">var</span> i <span class="keyword">interface</span>&#123;&#125; = p</span><br><span class="line">fmt.Println(i == <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">结果：</span><br><span class="line"><span class="literal">false</span></span><br></pre></td></tr></table></figure><p>这个坑可以采取避免将一个有可能为nil的具体类型的值赋值给interface变量尽量避免</p><h3 id="json中反序列化丢失精度的问题"><a href="#json中反序列化丢失精度的问题" class="headerlink" title="json中反序列化丢失精度的问题"></a>json中反序列化丢失精度的问题</h3><p>在json的规范中，对于数字类型是不区分整形和浮点型的。在使用<code>json.Unmarshal</code>进行json的反序列化的时候，如果没有指定数据类型（单纯使用interface{}作为接收变量）可能存在丢失精度的问题。因为如果没有指定具体的数据类型，其默认采用的float64作为其数字的接受类型，当数字的精度超过float能够表示的精度范围时就会造成精度丢失的问题。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">const</span> jsonStream = <span class="string">`&#123;"key": 675667567896876867896&#125;`</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> user <span class="keyword">interface</span>&#123;&#125;  <span class="comment">// 不指定反序列化的类型</span></span><br><span class="line">err := json.Unmarshal([]<span class="keyword">byte</span>(jsonStream), &amp;user)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">"error:"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">m := user.(<span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">interface</span>&#123;&#125;)</span><br><span class="line"></span><br><span class="line">finalStr, err:= json.Marshal(user)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">"error:"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fansCount := m[<span class="string">"key"</span>]</span><br><span class="line"></span><br><span class="line">fmt.Println(<span class="keyword">string</span>(finalStr))</span><br><span class="line">fmt.Printf(<span class="string">"%+v \n"</span>, reflect.TypeOf(fansCount).Name())</span><br><span class="line">fmt.Printf(<span class="string">"%+v \n"</span>, fansCount.(<span class="keyword">float64</span>))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Output:</span><br><span class="line">&#123;<span class="string">"key"</span>:<span class="number">675667567896876800000</span>&#125;</span><br><span class="line"><span class="keyword">float64</span> </span><br><span class="line"><span class="number">6.756675678968768e+20</span></span><br></pre></td></tr></table></figure><p>解决方法是使用<code>func (*Decoder) UseNumber</code>方法告诉反序列化JSON的数字类型</p><figure class="highlight golang"><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="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">const</span> jsonStream = <span class="string">`&#123;"key": 675667567896876867896&#125;`</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> user <span class="keyword">interface</span>&#123;&#125;  <span class="comment">// 不指定反序列化的类型</span></span><br><span class="line"></span><br><span class="line">decoder := json.NewDecoder(strings.NewReader(jsonStream))</span><br><span class="line">decoder.UseNumber()</span><br><span class="line">err := decoder.Decode(&amp;user)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">"error:"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">finalStr, err:= json.Marshal(user)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">"error:"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fmt.Println(<span class="keyword">string</span>(finalStr))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Output:</span><br><span class="line">&#123;<span class="string">"key"</span>:<span class="number">675667567896876867896</span>&#125;</span><br></pre></td></tr></table></figure><h2 id="初级"><a href="#初级" class="headerlink" title="初级"></a>初级</h2><h3 id="使用range对String迭代"><a href="#使用range对String迭代" class="headerlink" title="使用range对String迭代"></a>使用range对String迭代</h3><p>使用range迭代String的时候要注意，迭代的索引是你迭代的字符的第一个byte（这个字符可能是unicode字符/rune），所以这个索引值不一定是一个自然数的序列。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    data := <span class="string">"A\xfe\x02\xff\x04"</span></span><br><span class="line">    <span class="keyword">for</span> _,v := <span class="keyword">range</span> data &#123;</span><br><span class="line">        fmt.Printf(<span class="string">"%#x "</span>,v)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)</span></span><br><span class="line"></span><br><span class="line">    fmt.Println()</span><br><span class="line">    <span class="keyword">for</span> _,v := <span class="keyword">range</span> []<span class="keyword">byte</span>(data) &#123;</span><br><span class="line">        fmt.Printf(<span class="string">"%#x "</span>,v)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//prints: 0x41 0xfe 0x2 0xff 0x4 (good)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="没有导出的Struct字段将无法encoded"><a href="#没有导出的Struct字段将无法encoded" class="headerlink" title="没有导出的Struct字段将无法encoded"></a>没有导出的Struct字段将无法encoded</h3><p>在struct中小写字母开头的字段将无法encoded，此时你进行encode的时候这些字段将会是零值</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> MyData <span class="keyword">struct</span> &#123;</span><br><span class="line">One <span class="keyword">int</span></span><br><span class="line">two <span class="keyword">string</span></span><br><span class="line">&#125;</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">in := MyData&#123;<span class="number">1</span>,<span class="string">"two"</span>&#125;</span><br><span class="line">fmt.Printf(<span class="string">"%#v\n"</span>,in) <span class="comment">//prints main.MyData&#123;One:1, two:"two"&#125;</span></span><br><span class="line"></span><br><span class="line">encoded,_ := json.Marshal(in)</span><br><span class="line">fmt.Println(<span class="keyword">string</span>(encoded)) <span class="comment">//prints &#123;"One":1&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> out MyData</span><br><span class="line">json.Unmarshal(encoded,&amp;out)</span><br><span class="line"></span><br><span class="line">fmt.Printf(<span class="string">"%#v\n"</span>,out) <span class="comment">//prints main.MyData&#123;One:1, two:""&#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>参考：</p><ul><li><a href="http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/" target="_blank" rel="noopener">http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;汇总一些本人积累的Go语言中容易犯的错误（持续更新）&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="Go" scheme="https://www.liuin.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Redis 实践中的问题与优化</title>
    <link href="https://www.liuin.cn/2018/08/16/Redis-%E5%AE%9E%E8%B7%B5%E4%B8%AD%E7%9A%84%E9%97%AE%E9%A2%98%E4%B8%8E%E4%BC%98%E5%8C%96/"/>
    <id>https://www.liuin.cn/2018/08/16/Redis-实践中的问题与优化/</id>
    <published>2018-08-16T18:09:30.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>总结在常见的在Redis实践中遇到的问题和优化方法</p><a id="more"></a><h2 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h2><h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>缓存中存储的是数据库中命中的数据，如果没有命中，则会去数据库中去取然后回溯到缓存中。这个时候如果一直访问一个数据库中也没有的数据，就可能造成大量请求直接访问数据库。</p><h3 id="优化方案"><a href="#优化方案" class="headerlink" title="优化方案"></a>优化方案</h3><ol><li>如果从数据库中查询的结果也为空，那么将这个空结果也放到缓存中。这样可能会造成数据不一致的情况（具体取决数据库和缓存的一致性策略），所以要设置一个比较短的过期时间。</li><li>查询数据库的时候使用互斥锁，查询前首先要得到锁再去查询，没有得到锁的要先等待直到锁可用。但是这种方案可能减低其查询的性能，因为只能够有一个查询存在，所以对于不属于缓存穿透的场景的查询性能会收到影响。</li><li>知道key的规则或者合法性，可以在业务层就进行一个过滤（通过规则或者布隆过滤器）</li></ol><h2 id="缓存雪崩"><a href="#缓存雪崩" class="headerlink" title="缓存雪崩"></a>缓存雪崩</h2><h3 id="问题-1"><a href="#问题-1" class="headerlink" title="问题"></a>问题</h3><p>缓存中设置了失效时间，如果大量缓存在短时间内一起失效，这样访问的压力就都移到数据库中去了。就像发生雪崩一样。</p><h3 id="优化方案-1"><a href="#优化方案-1" class="headerlink" title="优化方案"></a>优化方案</h3><ul><li>采用加锁或消息队列，采用单线程的方式，防止失效时大量线程请求数据库。</li><li>在设置缓存时间的时候，在原来缓存时间的技术上加一个比较小的随机值，避免大量缓存在同一时间失效</li></ul><h2 id="热key问题"><a href="#热key问题" class="headerlink" title="热key问题"></a>热key问题</h2><h3 id="问题-2"><a href="#问题-2" class="headerlink" title="问题"></a>问题</h3><p>业务层可能因为一些新闻或者热点事件对一个相同的key在短时间内发出大量请求。如果此时没有命中缓存，就需要构建缓存，但是构建缓存需要时间，在这个时间里面所有对key的请求还是会打到下面的数据库上，造成后端和数据库的压力过大。同时热key本身对缓存系统也会带来非常大的压力。</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><ul><li>还是可以用互斥锁进行解决，确保同一时间只有一个线程查询数据库</li><li>对于大量请求打到Redis上的解决可以采用在本地构建local cache的方法</li></ul><h2 id="大key问题"><a href="#大key问题" class="headerlink" title="大key问题"></a>大key问题</h2><h3 id="问题-3"><a href="#问题-3" class="headerlink" title="问题"></a>问题</h3><p>Redis作为缓存通常是以接口为单位进行存储，也就是说Redis中存储的数据就是接口中返回的东西。</p><p>这样能够使得每次请求只有一个原子操作，但是带来的问题就是key会非常大，造成了大key存储的问题。这会导致用这个Redis作为缓存的服务非常不稳定，因为单次请求这个key的读写时间过长数据过多导致后面redis的原子操作可能发生I/O超时，从而导致redis实例命中率波动较大。当缓存命中率比较低的时候，瞬间大量数据请求就会打到数据库，导致带宽不足、数据库压力过大、数据库慢查询过多和平均查询时间变长。</p><p>因为我们服务的时间=redis超时时间+数据库慢查询时间+接口逻辑时间，所以大key可能大致整个服务的恶性循环。</p><h3 id="优化方案-2"><a href="#优化方案-2" class="headerlink" title="优化方案"></a>优化方案</h3><p>可以采用将存储单位从接口数据变为原子数据。每个接口数据都是由原子数据拼接而成，这个拼接可以在业务层完成。从而减小Redis请求超时的情况。</p><h2 id="批量请求问题"><a href="#批量请求问题" class="headerlink" title="批量请求问题"></a>批量请求问题</h2><h3 id="问题-4"><a href="#问题-4" class="headerlink" title="问题"></a>问题</h3><p>接着上面的问题，当把存储单位从接口数据变成了原子数据以后，势必会造成每个接口的请求数据变多。如果这些请求都通过单个连接去处理，就会造成总体的处理事件过长的问题</p><h3 id="优化方案-3"><a href="#优化方案-3" class="headerlink" title="优化方案"></a>优化方案</h3><p>这个时候可以使用到批量请求的相关接口：批量存取字符串类型的方式分别为mget、mset、pipeline。三者都会将多次操作合并到一次，也就是进行以此连接返回所有数据。但是之间的差异还是比较明显的，比如：</p><ul><li>pipeline：可以支持多个不同类型的操作在一次请求中，但是使用pipeline需要客户端和服务端都支持。当某个命令的执行需要依赖前一个命令的返回结果时，无法使用pipeline。</li><li>mget：只支持get请求，megt过大的时候会导致内存暴涨，然后一直持有不释放</li><li>mset：只支持set请求，且不支持设置过期时间</li></ul><p>参考：</p><ul><li><a href="https://blog.csdn.net/mayfla/article/details/80112241" target="_blank" rel="noopener">https://blog.csdn.net/mayfla/article/details/80112241</a></li><li><a href="https://www.cnblogs.com/rjzheng/p/9096228.html" target="_blank" rel="noopener">https://www.cnblogs.com/rjzheng/p/9096228.html</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;总结在常见的在Redis实践中遇到的问题和优化方法&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="Redis" scheme="https://www.liuin.cn/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>Git 闯关游戏——githug</title>
    <link href="https://www.liuin.cn/2018/08/08/Git-%E9%97%AF%E5%85%B3%E6%B8%B8%E6%88%8F%E2%80%94%E2%80%94githug/"/>
    <id>https://www.liuin.cn/2018/08/08/Git-闯关游戏——githug/</id>
    <published>2018-08-08T21:26:19.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>学习Git过程中缺乏实践？不如来玩一玩这款Git闯关游戏，通过git命令来闯关。玩着玩着就学会了Git，美滋滋！</p><a id="more"></a><h2 id="githug"><a href="#githug" class="headerlink" title="githug"></a>githug</h2><p>githug 是用Ruby开发为了帮助学习使用git的一个闯关游戏。和我之前推荐的学习Linux命令的<a href="https://www.liuin.cn/2017/11/01/Bandit-%E9%80%9A%E5%85%B3%E9%A2%98%E8%A7%A3/">Bandit</a>游戏有点像。</p><p>其Github地址：<a href="https://github.com/Gazler/githug" target="_blank" rel="noopener">https://github.com/Gazler/githug</a></p><p>安装过程也比较简单，首先安装好基本的Ruby环境（1.8.7）以上</p><p>因为国内可能会遇到用gem安装相关包因为https网络问题报错的情况，如果出现<code>Unable to download data from https://rubygems.org/</code>这样的报错信息</p><p>可以通过如下方式解决<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo gem sources -r https://rubygems.org</span><br><span class="line">sudo gem sources -a http://rubygems.org</span><br></pre></td></tr></table></figure></p><p>当然安装完以后也别忘了设置回来<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo gem sources -r http://rubygems.org</span><br><span class="line">sudo gem sources -a https://rubygems.org</span><br></pre></td></tr></table></figure></p><p>然后进行安装：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo gem install githug</span><br></pre></td></tr></table></figure></p><p>安装完成以后运行<code>githug</code>会提示创建相关的文件夹，回复<code>y</code>就行了。</p><p>githug有4个基本命令：<br><figure class="highlight bash"><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">play - 默认命令，查看当前状态和题目</span><br><span class="line">hint - 给你当前level的一些提示</span><br><span class="line">reset - 重新开始当前关卡</span><br><span class="line">levels - 列出所有关卡</span><br></pre></td></tr></table></figure></p><h2 id="通过题解"><a href="#通过题解" class="headerlink" title="通过题解"></a>通过题解</h2><p>有一些关卡比较简单，选取一些我觉得有点意思的关卡给出题解吧。</p><h3 id="20-commit-in-futue"><a href="#20-commit-in-futue" class="headerlink" title="20 commit_in_futue"></a>20 commit_in_futue</h3><p>使用一个未来的时间作为提交，提交的时候指定–date选项，能够自定义提交的时间</p><figure class="highlight bash"><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">$ git commit -m <span class="string">"future commit"</span> --date=12.12.2018T22:00:00</span><br><span class="line"></span><br><span class="line">[master (root-commit) 2f27bee] future commit</span><br><span class="line"> Date: Wed Dec 12 22:00:00 2018 +0800</span><br><span class="line"> 1 file changed, 0 insertions(+), 0 deletions(-)</span><br><span class="line"> create mode 100644 README</span><br></pre></td></tr></table></figure><h3 id="21-reset"><a href="#21-reset" class="headerlink" title="21 reset"></a>21 reset</h3><p>git reset 常用来进行版本回推。要了解<code>reset</code>命令首先要了解git中的三棵树：<br><code>HEAD</code>：是当前分支引用的指针，它总是指向该分支上的最后一次提交。 这表示 HEAD 将是下一次提交的父结点。<br><code>暂存区（Index）</code>：预期的下一次提交<br><code>工作区</code>：git 实际管理的文件的目录</p><p><img src="http://data3.liuin.cn/2018-09-16-15371061297360.jpg" alt=""></p><p><code>reset</code> 命令以一种简单可预见的方式直接操纵三棵树，它做了三个基本操作：移动 HAED，重置索引，重置工作目录。实际上 reset 是以特定的顺序来重写三棵树，并在指定相应选项时停止：</p><ol><li>移动 HEAD 分支的指向 （若指定了 –soft，则到此停止）</li><li>使索引看起来像 HEAD （–mixed，默认到此停止）</li><li>使工作目录看起来像索引 （若指定了 –hard，则进行这一步）</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset to_commit_second.rb</span><br></pre></td></tr></table></figure><h3 id="22-reset-soft"><a href="#22-reset-soft" class="headerlink" title="22 reset_soft"></a>22 reset_soft</h3><p>使用 reset 命令的 –soft 选项</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset --soft HEAD~</span><br></pre></td></tr></table></figure><h3 id="23-checkout-file"><a href="#23-checkout-file" class="headerlink" title="23 checkout_file"></a>23 checkout_file</h3><p>checkout 作用于文件的时候，会将文件从当前HEAD中检出，即让当前工作区的文件变成HEAD中的样子，命令格式为<code>git checkout [-q] [&lt;commit&gt;] [--] &lt;paths&gt;...</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout config.rb</span><br></pre></td></tr></table></figure><h3 id="30-blame"><a href="#30-blame" class="headerlink" title="30 blame"></a>30 blame</h3><p>可以通过 <code>git blame</code> 命令查看项目中具体哪一行代码是谁写的，什么时候引入的。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git blame config.rb</span><br></pre></td></tr></table></figure><h3 id="33-checkout-tag"><a href="#33-checkout-tag" class="headerlink" title="33 checkout_tag"></a>33 checkout_tag</h3><p>检出到指定标签，通过<code>checkout</code>命令实现，能够创建一个临时分支指向tag所在的提交</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout v1.2</span><br></pre></td></tr></table></figure><h3 id="34-checkout-tag-over-branch"><a href="#34-checkout-tag-over-branch" class="headerlink" title="34 checkout_tag_over_branch"></a>34 checkout_tag_over_branch</h3><p>有的时候会遇到标签和分支的名字一样的情况，通过在前面添加前缀<code>tags/</code>来指定是tag的名字</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout tags/v1.2</span><br></pre></td></tr></table></figure><h3 id="35-branch-at"><a href="#35-branch-at" class="headerlink" title="35 branch_at"></a>35 branch_at</h3><p>检出指定提交到一个新的命名分支</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b test_branch HEAD~</span><br></pre></td></tr></table></figure><h3 id="40-rebase"><a href="#40-rebase" class="headerlink" title="40 rebase"></a>40 rebase</h3><p>rebase命令可以用来整合来自不同分支</p><p><img src="http://data3.liuin.cn/2018-09-17-15371452668045.jpg" alt=""></p><p>如上图所示，提取在 C4 中引入的补丁和修改，然后在 C3 的基础上再应用一次。在 Git 中，这种操作就叫做 变基</p><p><code>git rebase [&lt;upstream&gt; [&lt;branch&gt;]</code> 命令将upstream的修改在branch上再应用一次</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout feature</span><br><span class="line">git rebase master</span><br></pre></td></tr></table></figure><h3 id="41-rebase-onto"><a href="#41-rebase-onto" class="headerlink" title="41 rebase-onto"></a>41 rebase-onto</h3><p>在对两个分支进行变基时，所生成的“重演”并不一定要在目标分支上应用，你也可以指定另外的一个分支进行应用。就像 从一个特性分支里再分出一个特性分支的提交历史 中的例子这样。</p><p><code>$ git rebase --onto master server client</code></p><p>以上命令的意思是：“取出 client 分支，找出处于 client 分支和 server 分支的共同祖先之后的修改，然后把它们在 master 分支上重演一遍”。</p><p>当master 和server 一致的时候，只需要写一个</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase --onto master wrong_branch</span><br></pre></td></tr></table></figure><h3 id="42-repack"><a href="#42-repack" class="headerlink" title="42 repack"></a>42 repack</h3><p>git repack 对松散对象进行打包，凡是有引用关联的对象都被打在包里，未被关联的对象仍旧以松散对象的形式保存</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git repack</span><br></pre></td></tr></table></figure><h3 id="43-cherry-pick"><a href="#43-cherry-pick" class="headerlink" title="43 cherry-pick"></a>43 cherry-pick</h3><p>应用某一个提交的修改到当前分支</p><figure class="highlight bash"><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 new-feature</span><br><span class="line">git <span class="built_in">log</span></span><br><span class="line">git cherry-pick ca32a6dac7b6f97975edbe19a4296c2ee7682f68</span><br></pre></td></tr></table></figure><h3 id="45-rename-commit"><a href="#45-rename-commit" class="headerlink" title="45 rename commit"></a>45 rename commit</h3><p>当涉及提交修改时，应该想到 <code>git rebase -i</code> 命令，它接受可以一个参数（提交的哈希值），它将罗列出此提交之后的所有提交，将此提交作为一个root commit。用途为修改提交历史，其后跟一个某一条提交日志的哈希值，表示要修改这条日志之前的提交历史。</p><p>输入命令以后，会将提交从前到后显示。每一行的前面有一个命令词，表示对此次更新执行什么操作。</p><p>类似下面这种：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pick 06973a3 First coommit</span><br><span class="line">pick 771b71d Second commit</span><br></pre></td></tr></table></figure><p>前面的命令有以下几种：</p><ul><li>“pick”，表示执行此次提交；</li><li>“reword”，表示执行此次提交，但要修改备注内容；</li><li>“edit”，表示可以修改此次提交，比如再追加文件或修改文件；</li><li>“squash”，表示把此次提交的内容合并到上次提交中，备注内容也合并到上次提交中；</li><li>“fixup”，和 “squash” 类似，但会丢弃掉此次备注内容；</li><li>“exec”，执行命令行下的命令；</li><li>“drop”，删除此次提交。</li></ul><p>这一关是需要把第2次提交错误额comment修改过来，所以对第二次提交执行reword并修改备注就行了</p><h3 id="46-squash"><a href="#46-squash" class="headerlink" title="46 squash"></a>46 squash</h3><p>把多次修改合并成一次，这里用到的还是上面的<code>git rebase -i</code>的命令，只不过是将后面动作命令都变成<code>squash</code></p><h3 id="47-merge-squash"><a href="#47-merge-squash" class="headerlink" title="47 merge_squash"></a>47 merge_squash</h3><p>为了把分支的多次提交合并为主干上的一次提交，可以在merge命令后面加一个 squash 参数</p><p><code>git merge branch-name --squash</code></p><p>过关命令：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git merge long-feature-branch --squash</span><br><span class="line">git commit -m <span class="string">"comment"</span></span><br></pre></td></tr></table></figure></p><h3 id="48-reorder"><a href="#48-reorder" class="headerlink" title="48 reorder"></a>48 reorder</h3><p>你提交了几次但是提交顺序多了，想把顺序换一下，这里用到的还是<code>git rebase -i</code>命令。需要把几次提交的顺序更换一下即可。</p><h3 id="49-bisect"><a href="#49-bisect" class="headerlink" title="49 bisect"></a>49 bisect</h3><p>在程序持续迭代的过程中不免会引入 bug，除了定位 bug 的代码片断，我们还想知道 bug 是在什么时间被引入的，这时就可以<strong>借助 Git 提供的 bisect 工具来查找是哪次提交引入了 bug</strong>。bisect 是用二分法来查找的，就像用二分查找法查找数组元素那样。</p><p>其查找流程也比较简单，首先确定查找的commit范围，然后在每一次二分查找时给出程序执行是否正确的判断。这个时候bisect就会自动进行二分查找。</p><figure class="highlight bash"><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">$ git bisect start</span><br><span class="line">$ git bisect good f608824888b</span><br><span class="line">$ git bisect bad 12628f463f4</span><br><span class="line">$ make <span class="built_in">test</span></span><br><span class="line">$ git bisect good</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3 id="50-stage-lines"><a href="#50-stage-lines" class="headerlink" title="50 stage_lines"></a>50 stage_lines</h3><p>用 <code>git add</code> 命令可以把文件添加到暂存区，但如果你不想把文件中的全部修改都提交到暂存区，或者说你只想把文件中的部分修改提交到缓存区，那么你需要加上<code>edit</code>参数</p><p>这时 Git 会自动打开文本编辑器，编辑的内容就是 <code>git diff</code> 命令的结果，这时你就可以编辑2个文件之间的差异，只保留要提交到暂存区的差异，而删除不需要提交到暂存区的差异，然后保存退出，Git 就会按你编辑过的差异把相应的内容提交到暂存区。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git add file-name --edit</span><br></pre></td></tr></table></figure><h3 id="51-find-old-branch"><a href="#51-find-old-branch" class="headerlink" title="51 find_old_branch"></a>51 find_old_branch</h3><p>使用<code>git reflog</code> 可以查看历史所在的分支和提交在哪里</p><h3 id="52-revert"><a href="#52-revert" class="headerlink" title="52 revert"></a>52 revert</h3><p><code>git revert</code> 命令能够对某一次提交执行逆操作，这适用于某次提交出现问题的情况，添加<code>--no-edit</code>能够让系统自动生成备注</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">log</span></span><br><span class="line">git revert 3873913 --no-edit</span><br></pre></td></tr></table></figure><h3 id="55-submodule"><a href="#55-submodule" class="headerlink" title="55 submodule"></a>55 submodule</h3><p>如果你想把别人的仓库代码作为自己项目一个库来使用，可以采用模块化的思路，把这个库作为模块进行管理。Git 专门提供了相应的工具，用如下命令把第三方仓库作为模块引入：</p><p><code>git submodule add module-url</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git submodule add https://github.com/jackmaney/githug-include-me</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="http://wiki.jikexueyuan.com/project/githug-walkthrough/" target="_blank" rel="noopener">http://wiki.jikexueyuan.com/project/githug-walkthrough/</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;学习Git过程中缺乏实践？不如来玩一玩这款Git闯关游戏，通过git命令来闯关。玩着玩着就学会了Git，美滋滋！&lt;/p&gt;
    
    </summary>
    
      <category term="软件使用" scheme="https://www.liuin.cn/categories/%E8%BD%AF%E4%BB%B6%E4%BD%BF%E7%94%A8/"/>
    
    
      <category term="Git" scheme="https://www.liuin.cn/tags/Git/"/>
    
  </entry>
  
  <entry>
    <title>算法总结——二分法</title>
    <link href="https://www.liuin.cn/2018/08/05/%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93%E2%80%94%E2%80%94%E4%BA%8C%E5%88%86%E6%B3%95/"/>
    <id>https://www.liuin.cn/2018/08/05/算法总结——二分法/</id>
    <published>2018-08-05T22:23:49.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>汇集二分法常见的题目</p><a id="more"></a><h2 id="二分法"><a href="#二分法" class="headerlink" title="二分法"></a>二分法</h2><p>二分法是一个非常常用的算法技巧，用于在多条排序记录中快速找到待查找的记录。相比遍历需要O(n)将时间复杂度优化到了O(logn)，但是需要一组序列本身是有序的。</p><p>写二分代码的关键在于<strong>处理好其边界情况</strong></p><p>下面是一个标准的二分代码：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">search</span><span class="params">(<span class="keyword">int</span> <span class="built_in">array</span>[], <span class="keyword">int</span> n, <span class="keyword">int</span> v)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> left, right, middle;</span><br><span class="line">    <span class="comment">// 赋初值，判断是左闭右闭还是左闭右开</span></span><br><span class="line">    left = <span class="number">0</span>, right = n - <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// 退出条件，有小于和小于等于两种写法</span></span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 中值计算，有向下取整和向上取整</span></span><br><span class="line">        middle = (left + right) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">array</span>[middle] &gt; v)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// 边界值更新，判断是否需要加一</span></span><br><span class="line">            right = middle;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">array</span>[middle] &lt; v)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// 边界值更新，判断是否需要加一</span></span><br><span class="line">            left = middle;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> middle;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>正如上面的注释中所示，写好一个二分算法只需要注意这4个地方：</p><ol><li>赋初值，判断是左闭右闭还是左闭右开</li><li>退出条件，有小于和小于等于两种写法，小于等于对应的是上面“右闭”的情况，因为最后一个可能是要查找的值</li><li>中值计算，有向下取整和向上取整两种方案</li><li>边界值更新，加一或者不变，根据实际情况进行判断（一般是判断是否需要舍弃这一个值）</li></ol><h3 id="左右边界的开闭"><a href="#左右边界的开闭" class="headerlink" title="左右边界的开闭"></a>左右边界的开闭</h3><p>左闭右闭还是左闭右开应该对应不同的写法，主要的区别在于最后一个值的舍弃问题</p><figure class="highlight cpp"><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"><span class="comment">// 左闭右闭 [0, n-1] 写法</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">search2</span><span class="params">(<span class="keyword">int</span> <span class="built_in">array</span>[], <span class="keyword">int</span> n, <span class="keyword">int</span> v)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> left, right, middle;</span><br><span class="line"></span><br><span class="line">    left = <span class="number">0</span>, right = n - <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right)</span><br><span class="line">    &#123;</span><br><span class="line">        middle = (left + right) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">array</span>[middle] &gt; v)</span><br><span class="line">        &#123;</span><br><span class="line">            right = middle - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">array</span>[middle] &lt; v)</span><br><span class="line">        &#123;</span><br><span class="line">            left = middle + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> middle;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 左闭右开 [0, n) 写法</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">search3</span><span class="params">(<span class="keyword">int</span> <span class="built_in">array</span>[], <span class="keyword">int</span> n, <span class="keyword">int</span> v)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> left, right, middle;</span><br><span class="line"></span><br><span class="line">    left = <span class="number">0</span>, right = n;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (left &lt; right)</span><br><span class="line">    &#123;</span><br><span class="line">        middle = (left + right) / <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">array</span>[middle] &gt; v)</span><br><span class="line">        &#123;</span><br><span class="line">            right = middle;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">array</span>[middle] &lt; v)</span><br><span class="line">        &#123;</span><br><span class="line">            left = middle + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> middle;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="死循环"><a href="#死循环" class="headerlink" title="死循环"></a>死循环</h3><p>一般如果向下取整和左边界更新不加一组合就可能造成死循环，同理向上取整和右边界不减一组合也可能造成死循环</p><h3 id="溢出问题"><a href="#溢出问题" class="headerlink" title="溢出问题"></a>溢出问题</h3><p>在一些比较特殊的情况下<code>middle = (left + right) / 2</code>这种写法可能造成溢出问题，更加保险的写法是<code>middle = left + (right - left) / 2;</code></p><h3 id="比较完善的写法"><a href="#比较完善的写法" class="headerlink" title="比较完善的写法"></a>比较完善的写法</h3><p>《编程珠玑》中提供了一个给出了比较完成的二分法代码写法：</p><figure class="highlight cpp"><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="function"><span class="keyword">int</span> <span class="title">search4</span><span class="params">(<span class="keyword">int</span> <span class="built_in">array</span>[], <span class="keyword">int</span> n, <span class="keyword">int</span> v)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> left, right, middle;</span><br><span class="line">    left = <span class="number">-1</span>, right = n;</span><br><span class="line">    <span class="keyword">while</span> (left + <span class="number">1</span> != right)</span><br><span class="line">    &#123;</span><br><span class="line">        middle = left + （right － left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">array</span>[middle] &lt; v)</span><br><span class="line">        &#123;</span><br><span class="line">            left = middle;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            right = middle;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (right &gt;= n || <span class="built_in">array</span>[right] != v)</span><br><span class="line">        right = <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> right;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2018春招腾讯笔试题"><a href="#2018春招腾讯笔试题" class="headerlink" title="2018春招腾讯笔试题"></a>2018春招腾讯笔试题</h2><p><a href="https://www.nowcoder.com/questionTerminal/d732267e73ce4918b61d9e3d0ddd9182" target="_blank" rel="noopener">试题地址</a></p><h3 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h3><p>小Q的父母要出差N天，走之前给小Q留下了M块巧克力。小Q决定每天吃的巧克力数量不少于前一天吃的一半，但是他又不想在父母回来之前的某一天没有巧克力吃，请问他第一天最多能吃多少块巧克力 </p><h3 id="题解"><a href="#题解" class="headerlink" title="题解"></a>题解</h3><p>可以构造出理想的情况应该是：前面是一个数开始，然后不断除2（向上取整），直到变为1，最后一直持续1。这样把这个序列分为两段：前面类似等比序列，后面都是1</p><p>这样我们知道序列的第一个数就知道了以这个数开头的序列总和最少是多少（按照上面的规则计算），这样我们可以从<code>m-n+1</code>开始一直往<code>1</code>当做第一个数遍历，找到第一个满足最小总和少于m的数。便能够得到答案。</p><p>这种情况和二分法的常见题型是不是很像，找到第一个满足某种情况的数，而且这个序列还是一个单调序列。用一个二分法加速</p><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><figure class="highlight cpp"><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"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;cstdio&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;cstdlib&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;algorithm&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sum</span><span class="params">(<span class="keyword">int</span> n, <span class="keyword">int</span> p)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(p--)</span><br><span class="line">    &#123;</span><br><span class="line">        sum += n;</span><br><span class="line">        n = (n+<span class="number">1</span>)/<span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> n, m;</span><br><span class="line">    <span class="built_in">cin</span>&gt;&gt;n&gt;&gt;m;</span><br><span class="line">    <span class="keyword">int</span> l = <span class="number">1</span>, r = m-n+<span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span>(l&lt;r)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">int</span> tem = (l+r+<span class="number">1</span>)/<span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span>(sum(tem, n) &lt;= m)</span><br><span class="line">            l = tem;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            r = tem - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;l&lt;&lt;<span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    solve();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="POJ-3258"><a href="#POJ-3258" class="headerlink" title="POJ 3258"></a>POJ 3258</h2><p><a href="http://poj.org/problem?id=3258" target="_blank" rel="noopener">River Hopscotch</a></p><h3 id="题意-1"><a href="#题意-1" class="headerlink" title="题意"></a>题意</h3><p>给出一条河对岸中n个石子的坐标（加上起点和终点），现在移走m个石子，要求两个石子间的最小值最大，这个最大的最小值</p><h3 id="题解-1"><a href="#题解-1" class="headerlink" title="题解"></a>题解</h3><p>这个一道经典的二分题目，最大化最小值问题。在查找过程中判断一个数是否满足条件的时候，我们可以转化成判断满足这个最小值需要移走的石子数，通过跟给定的石子数进行比较来判断这个数是否满足条件，最后通过二分找到这个最大的值。</p><h3 id="代码-1"><a href="#代码-1" class="headerlink" title="代码"></a>代码</h3><figure class="highlight cpp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> INF 0x3f3f3f3f</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">long</span> <span class="keyword">long</span> ll;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> maxn=<span class="number">50005</span>;</span><br><span class="line"><span class="keyword">int</span> l,n,m;</span><br><span class="line"><span class="keyword">int</span> d[maxn];</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">judge</span><span class="params">(<span class="keyword">int</span> s)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">int</span> j=<span class="number">0</span>,num=<span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">1</span>;i&lt;n;i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span>(d[i]-d[j]&lt;s) num++;</span><br><span class="line"><span class="keyword">else</span> j=i;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//cout&lt;&lt;"s:"&lt;&lt;s&lt;&lt;"num:"&lt;&lt;num&lt;&lt;"m:"&lt;&lt;m&lt;&lt;endl;</span></span><br><span class="line"><span class="keyword">return</span> num&lt;=m;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">int</span> ll=<span class="number">0</span>,rr=l+<span class="number">1</span>;</span><br><span class="line"><span class="keyword">int</span> mid;</span><br><span class="line"><span class="keyword">while</span>(ll+<span class="number">1</span>&lt;rr)</span><br><span class="line">&#123;</span><br><span class="line">mid=(ll+rr)&gt;&gt;<span class="number">1</span>;</span><br><span class="line"><span class="comment">//cout&lt;&lt;ll&lt;&lt;" "&lt;&lt;rr&lt;&lt;" "&lt;&lt;mid&lt;&lt;endl;</span></span><br><span class="line"><span class="keyword">if</span>(judge(mid))</span><br><span class="line">ll=mid;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">rr=mid;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> ll;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">while</span>(<span class="built_in">scanf</span>(<span class="string">"%d %d %d"</span>,&amp;l,&amp;n,&amp;m)!=EOF)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">1</span>;i&lt;=n;i++)</span><br><span class="line"><span class="built_in">scanf</span>(<span class="string">"%d"</span>,&amp;d[i]);</span><br><span class="line">        d[<span class="number">0</span>]=<span class="number">0</span>;</span><br><span class="line">d[n+<span class="number">1</span>]=l;</span><br><span class="line">n+=<span class="number">2</span>;</span><br><span class="line">sort(d,d+n);</span><br><span class="line"><span class="keyword">int</span> ans=fun();</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%d\n"</span>,ans);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="POJ-3273"><a href="#POJ-3273" class="headerlink" title="POJ 3273"></a>POJ 3273</h2><p><a href="http://poj.org/problem?id=3273" target="_blank" rel="noopener">Monthly Expense</a></p><h3 id="题意-2"><a href="#题意-2" class="headerlink" title="题意"></a>题意</h3><p>给出包含n个元素的数组，将这n个元素分成最多m段，问各种分法中每段和的最大值得最小值是多少</p><h3 id="题解-2"><a href="#题解-2" class="headerlink" title="题解"></a>题解</h3><p>最小化最大值问题，使用二分进行求解，需要注意的是，在不断二分的时候边界更新的时候，当中间值不满足条件的时候，新的区间应该是[mid+1,r]，满足条件的时候新的区间应该是[l,mic].即要排除掉不满足条件的数</p><h3 id="代码-2"><a href="#代码-2" class="headerlink" title="代码"></a>代码</h3><figure class="highlight cpp"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> INF 0x3f3f3f3f</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">long</span> <span class="keyword">long</span> ll;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> maxn=<span class="number">100010</span>;</span><br><span class="line"><span class="keyword">int</span> n,m;</span><br><span class="line"><span class="keyword">int</span> d[maxn];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">judge</span><span class="params">(<span class="keyword">int</span> s)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> num=<span class="number">0</span>,tmp=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;n;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span>(tmp+d[i]&lt;=s) tmp+=d[i];</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            tmp=d[i];</span><br><span class="line">            num++;</span><br><span class="line">        &#125;   </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(tmp) num++;</span><br><span class="line">    <span class="keyword">return</span> num&lt;=m;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">(<span class="keyword">int</span> maxv,<span class="keyword">int</span> sum)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> l=maxv,r=sum,mid;</span><br><span class="line">    <span class="keyword">while</span>(l&lt;r)</span><br><span class="line">    &#123;</span><br><span class="line">        mid=(l+r)/<span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span>(judge(mid)) r=mid;</span><br><span class="line">        <span class="keyword">else</span> l=mid+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> l;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">solve</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(<span class="built_in">scanf</span>(“%d %d”,&amp;n,&amp;m)!=EOF)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">int</span> sum=<span class="number">0</span>,maxv=<span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;n;i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">scanf</span>(“%d”,&amp;d[i]);</span><br><span class="line">            sum+=d[i];</span><br><span class="line">            maxv=max(maxv,d[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">printf</span>(“%d\n”,fun(maxv,sum));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;汇集二分法常见的题目&lt;/p&gt;
    
    </summary>
    
      <category term="算法" scheme="https://www.liuin.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="二分" scheme="https://www.liuin.cn/tags/%E4%BA%8C%E5%88%86/"/>
    
      <category term="算法总结" scheme="https://www.liuin.cn/tags/%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>LeetCode——单调数据结构</title>
    <link href="https://www.liuin.cn/2018/07/27/LeetCode%E2%80%94%E2%80%94%E5%8D%95%E8%B0%83%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    <id>https://www.liuin.cn/2018/07/27/LeetCode——单调数据结构/</id>
    <published>2018-07-27T23:57:14.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>总结在LeetCode中遇到的单调栈的一系列题目</p><a id="more"></a><h2 id="单调栈"><a href="#单调栈" class="headerlink" title="单调栈"></a>单调栈</h2><p>单调栈是这样的一种数据结构：栈中从栈底到栈顶的数都是递减的，为了维护这种结构在插入比当前栈顶大的数的时候都需要先将栈顶的数弹出，这样我们就能够知道弹出的这个数两边比它大的数了。在某些题目中，单调栈的这种特定能够给我们提供很大的帮助。</p><h3 id="LeetCode-84"><a href="#LeetCode-84" class="headerlink" title="LeetCode 84"></a>LeetCode 84</h3><p><a href="https://leetcode.com/problems/largest-rectangle-in-histogram/description/" target="_blank" rel="noopener">Largest Rectangle in Histogram</a></p><p>题意：<br>给出一个直方图，求直方图中所能够围成矩形的最大面积<br><img src="http://data3.liuin.cn/2018-09-08-15364073032884.jpg" alt=""></p><p>题解：<br>维护一个递增的单调栈，如果需要将栈顶数据弹出则表示形成了山峰的形状，这样我们就可以计算出这个山峰里能够围成的矩形的面积，因为弹出的这一个数能够知道其左右比它小的数。当遍历完以后还有一些矩形需要计算，再将这个栈依次弹出，这个时候右边没有比它小的数了，他左边比它小的数就是栈顶的数。每弹出一个数就能够计算出一个矩形的面积。</p><p>代码：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 维护单调栈</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">largestRectangleArea</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; heights)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> len = heights.size();</span><br><span class="line">        <span class="built_in">stack</span>&lt;<span class="keyword">int</span>&gt; s;</span><br><span class="line">        <span class="keyword">int</span> res = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">while</span>(!s.empty() &amp;&amp; heights[s.top()] &gt; heights[i])</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">int</span> tem = s.top();</span><br><span class="line">                s.pop();</span><br><span class="line">                <span class="comment">// 计算面积</span></span><br><span class="line">                <span class="keyword">int</span> curArea = s.empty() ? i*heights[tem] : (i - s.top() - <span class="number">1</span>)*heights[tem];</span><br><span class="line">                res = max(res, curArea);</span><br><span class="line">            &#125;</span><br><span class="line">            s.push(i);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span>(!s.empty())</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> tem = s.top();</span><br><span class="line">            s.pop();</span><br><span class="line">            <span class="keyword">int</span> curArea = s.empty() ? len*heights[tem] : (len - s.top() - <span class="number">1</span>) * heights[tem];</span><br><span class="line">            res = max(res, curArea);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h3 id="LeetCode-85"><a href="#LeetCode-85" class="headerlink" title="LeetCode 85"></a>LeetCode 85</h3><p><a href="https://leetcode.com/problems/maximal-rectangle/description/" target="_blank" rel="noopener">Maximal Rectangle</a></p><p>题意：<br>给出一个只包含0和1的二维矩阵，求出其中1围成的矩形的最大面积</p><p>题解：<br>这道题可以说是上面那道题的变形，因为把这个n*m的矩阵转换成m个直方图就是上面的那道题，计算m次即可</p><p>代码：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; getSlice(<span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">char</span>&gt;&gt;&amp; matrix, <span class="keyword">int</span> h)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; ret;</span><br><span class="line">        <span class="keyword">int</span> len = matrix[<span class="number">0</span>].size();</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> tem = h, count = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">while</span>(tem &gt;= <span class="number">0</span> &amp;&amp; matrix[tem][i] == <span class="string">'1'</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                tem --;</span><br><span class="line">                count ++;</span><br><span class="line">            &#125;</span><br><span class="line">            ret.push_back(count);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ret;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">maximalRectangle</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">char</span>&gt;&gt;&amp; matrix)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> len1 = matrix.size();</span><br><span class="line">        <span class="keyword">if</span>(len1 == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">int</span> len2 = matrix[<span class="number">0</span>].size();</span><br><span class="line">        <span class="keyword">if</span>(len2 == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">int</span> res = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> t = <span class="number">0</span>; t &lt; len1; t++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; heights = getSlice(matrix, t);</span><br><span class="line">            <span class="keyword">int</span> len3 = heights.size();</span><br><span class="line">            <span class="comment">// 使用栈来保存一个递增的序列</span></span><br><span class="line">            <span class="built_in">stack</span>&lt;<span class="keyword">int</span>&gt; s;</span><br><span class="line">            <span class="keyword">int</span> tem_res = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len3; i++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">while</span>(!s.empty() &amp;&amp; heights[s.top()] &gt; heights[i])</span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="keyword">int</span> tem = s.top();</span><br><span class="line">                    s.pop();</span><br><span class="line">                    <span class="comment">// 计算不在序列内的区块的面积</span></span><br><span class="line">                    <span class="keyword">int</span> curArea = s.empty() ? i*heights[tem] : (i - s.top() - <span class="number">1</span>)*heights[tem];</span><br><span class="line">                    tem_res = max(tem_res, curArea);</span><br><span class="line">                    <span class="comment">// cout&lt;&lt;i&lt;&lt;" "&lt;&lt;tem_res&lt;&lt;endl;</span></span><br><span class="line">                &#125;</span><br><span class="line">                s.push(i);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 对递增序列中的序列面积进行计算</span></span><br><span class="line">            <span class="keyword">while</span>(!s.empty())</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">int</span> tem = s.top();</span><br><span class="line">                s.pop();</span><br><span class="line">                <span class="keyword">int</span> curArea = s.empty() ? len3*heights[tem] : (len3 - s.top() - <span class="number">1</span>) * heights[tem];</span><br><span class="line">                tem_res = max(tem_res, curArea);</span><br><span class="line">                <span class="comment">// cout&lt;&lt;tem&lt;&lt;" "&lt;&lt;tem_res&lt;&lt;endl;</span></span><br><span class="line">            &#125;</span><br><span class="line">            res = max(res, tem_res);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h3 id="LeetCode-456"><a href="#LeetCode-456" class="headerlink" title="LeetCode 456"></a>LeetCode 456</h3><p><a href="https://leetcode.com/problems/132-pattern/description/" target="_blank" rel="noopener">132 Pattern</a></p><p>题意：</p><p>给出一个数组，判断是否存在这样的三个数：i &lt; j &lt; k，同时a[i] &lt; a[k] &lt; a[j] 类似于132的组合</p><p>题解：<br>从后往前去维护一个递减的单调栈，同时记录淘汰掉的数的最大值s3（这里相当于a[k]），从后往前遍历的过程中如果有数比s3要小，则表明存在上述的结构</p><p>代码：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 单调栈结构巧妙解法</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">find132pattern</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="built_in">stack</span>&lt;<span class="keyword">int</span>&gt; s;</span><br><span class="line">        <span class="keyword">int</span> len = nums.size();</span><br><span class="line">        <span class="keyword">if</span> (len &lt; <span class="number">3</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">int</span> s3 = INT_MIN;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = len - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span>(nums[i] &lt; s3) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">while</span>(!s.empty() &amp;&amp; nums[i] &gt; s.top())</span><br><span class="line">            &#123;</span><br><span class="line">                s3 = max(s3, s.top());</span><br><span class="line">                s.pop();</span><br><span class="line">            &#125;</span><br><span class="line">            s.push(nums[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h2 id="单调队列"><a href="#单调队列" class="headerlink" title="单调队列"></a>单调队列</h2><p>单调队列能够维护一个滑动窗口的最大值或者最小值</p><p>一般队列中存放的是数组中的index。以最大值为例，队列加数时：判断最后一个数是不是小于等于当前数，如果如果是就弹出最后这个数，不断重复直到无法弹出最后那个数后加当前数加入队尾，使得这个队列中是单调的。队列减数：通过index的差值判断在不在窗口内，通过差值计算是否值是否过期。</p><h3 id="LeetCode-239"><a href="#LeetCode-239" class="headerlink" title="LeetCode 239"></a>LeetCode 239</h3><p><a href="https://leetcode.com/problems/sliding-window-maximum/description/" target="_blank" rel="noopener">题目链接</a></p><p>典型的滑动窗口内的最大值问题，这里要求每一个滑动窗口的最大值并加入到一个动态数组中</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 单调队列数据结构</span></span><br><span class="line">    <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; maxSlidingWindow(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums, <span class="keyword">int</span> k) &#123;</span><br><span class="line">        <span class="keyword">int</span> len = nums.size(), l = <span class="number">0</span>, r = <span class="number">0</span>;</span><br><span class="line">        <span class="built_in">deque</span>&lt;<span class="keyword">int</span>&gt; de;</span><br><span class="line">        <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; res;</span><br><span class="line">        <span class="keyword">for</span>(; r &lt; len; r++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">while</span>(!de.empty() &amp;&amp; nums[r] &gt;= nums[de.back()])</span><br><span class="line">                de.pop_back();</span><br><span class="line">            de.push_back(r);</span><br><span class="line">            <span class="keyword">if</span>(r &gt;= k<span class="number">-1</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                res.push_back(nums[de.front()]);</span><br><span class="line">                <span class="keyword">if</span>(de.front() &lt;= l)</span><br><span class="line">                    de.pop_front();</span><br><span class="line">                l++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;总结在LeetCode中遇到的单调栈的一系列题目&lt;/p&gt;
    
    </summary>
    
      <category term="算法" scheme="https://www.liuin.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="LeetCode" scheme="https://www.liuin.cn/tags/LeetCode/"/>
    
      <category term="算法总结" scheme="https://www.liuin.cn/tags/%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>Redis 代理方案 Twemproxy</title>
    <link href="https://www.liuin.cn/2018/07/20/Redis-%E4%BB%A3%E7%90%86%E6%96%B9%E6%A1%88-Twemproxy/"/>
    <id>https://www.liuin.cn/2018/07/20/Redis-代理方案-Twemproxy/</id>
    <published>2018-07-20T20:51:51.000Z</published>
    <updated>2019-01-06T14:12:57.000Z</updated>
    
    <content type="html"><![CDATA[<p>谈谈在各大公司广泛使用的Redis代理方案——Twemproxy</p><a id="more"></a><p>Redis现在使用得非常广泛，但是Redis单例比较大的局限——单例使用到的内存一般最多10~20GB。这无法支撑大型线上业务系统的需求。而且也会造成资源的利用率过低——现在企业使用的内存肯定是Redis单例使用到内存的好几倍。</p><p>为了解决单机承载能力不足的问题，就必然会使用到多个Redis构成的集群。</p><h2 id="Redis-集群技术"><a href="#Redis-集群技术" class="headerlink" title="Redis 集群技术"></a>Redis 集群技术</h2><h3 id="客户端分片"><a href="#客户端分片" class="headerlink" title="客户端分片"></a>客户端分片</h3><p>客户端分片将分片工作放到了业务层，程序代码根据预先设置的路由规则，直接对多个Redis实例进行分布式访问。</p><p>这样的好处是，不依赖于第三方分布式中间件，实现方法和代码都自己掌控，可随时调整，不用担心踩到坑；同时，这种分片机制的性能比代理式更好（少了一个中间分发环节）。</p><p>但是缺点也比较多：首先这是一种静态分片技术。Redis实例的增减，都得手工调整分片程序，其次，虽然少了中间分发环节，但是导致升级麻烦，对研发人员的个人依赖性强——需要有较强的程序开发能力做后盾。</p><p>所以，这种方式下，可运维性较差。出现故障，定位和解决都得研发和运维配合着解决，故障时间变长。</p><h3 id="代理分片"><a href="#代理分片" class="headerlink" title="代理分片"></a>代理分片</h3><p>代理分片将分片工作交给专门的代理程序来做。代理程序接收到来自业务程序的数据请求，根据路由规则，将这些请求分发给正确的Redis实例并返回给业务程序。因此<strong>整个代理对业务层是透明的</strong>，业务层只需要把这个一个单纯的Redis实例使用即可。同时因为Redis请求都打到了代理上，我们很容易在代理的基础上进行进一步的分析工作。</p><p>虽然会因此带来些性能损耗，但对于Redis这种内存读写型应用，相对而言是能容忍的。</p><p>基于该机制的开源产品Twemproxy，便是其中代表之一，应用非常广泛。</p><p><img src="http://data3.liuin.cn/2018-09-08-15363953307171.jpg" alt=""></p><h3 id="Redis-Cluster"><a href="#Redis-Cluster" class="headerlink" title="Redis Cluster"></a>Redis Cluster</h3><p>在这种机制下，没有中心节点（和代理模式的重要不同之处）。</p><p>这样的好处是，Redis Cluster将所有Key映射到16384个Slot中，集群中每个Redis实例负责一部分，业务程序通过集成的Redis Cluster客户端进行操作。客户端可以向任一实例发出请求，如果所需数据不在该实例中，则该实例引导客户端自动去对应实例读写数据。</p><p>但是缺点也是同样存在，这是一个非常重的方案，Redis Cluster的成员管理（节点名称、IP、端口、状态、角色）等，都通过节点之间两两通讯，定期交换并更新。缺少了Redis单例的“简单、可依赖”的特点</p><h2 id="Twemproxy"><a href="#Twemproxy" class="headerlink" title="Twemproxy"></a>Twemproxy</h2><p>Twemproxy是上面代理分片构造Redis集群的一个开源解决方案。</p><p>TwemProxy采用中间层代理的方式，在不改动服务器端程序的情况下，使得集群管理更简单、轻量和有效。Twemproxy 通过引入一个代理层，将其后端的多台 Redis实例进行统一管理与分配，使应用程序只需要在Twemproxy 上进行操作，而不用关心后面具体有多少个真实的 Redis实例。</p><h3 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h3><ul><li>单线程工作，用C语言开发</li><li>直接支持大部分Redis指令，所以对于业务层可以透明使用</li><li>路由策略多样，支持HashTag, 通过HashTag可以自己设定将同一类型的key映射到同一个实例上去</li><li>减少与redis的直接连接数，保持与redis的长连接，可设置代理与后台每个redis连接的数目</li><li>自带一致性hash算法，能够将数据自动分片到后端多个redis实例上</li><li>支持redis pipelining request，将多个连接请求，组成reids pipelining统一向redis请求。</li><li>高效；对连接的管理采用epoll机制，内部数据传输采用“Zero Copy”技术，以提高运行效率</li></ul><h3 id="高可用方案"><a href="#高可用方案" class="headerlink" title="高可用方案"></a>高可用方案</h3><p>因为Twemproxy本身是单点，所以需要用Keepalived做高可用方案。</p><p>Keepalived是一种实现高可用的方案，它的功能主要包括两方面：<br>1）通过IP漂移，实现服务的高可用：服务器集群共享一个虚拟IP，同一时间只有一个服务器占有虚拟IP并对外提供服务，若该服务器不可用，则虚拟IP漂移至另一台服务器并对外提供服务；<br>2）对LVS应用服务层的应用服务器集群进行状态监控：若应用服务器不可用，则keepalived将其从集群中摘除，若应用服务器恢复，则keepalived将其重新加入集群中。</p><p><img src="http://data3.liuin.cn/2018-09-08-15363965065269.jpg" alt=""></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;谈谈在各大公司广泛使用的Redis代理方案——Twemproxy&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="Redis" scheme="https://www.liuin.cn/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>LeetCode总结——和为定值</title>
    <link href="https://www.liuin.cn/2018/07/16/LeetCode%E6%80%BB%E7%BB%93%E2%80%94%E2%80%94%E5%92%8C%E4%B8%BA%E5%AE%9A%E5%80%BC/"/>
    <id>https://www.liuin.cn/2018/07/16/LeetCode总结——和为定值/</id>
    <published>2018-07-16T17:48:48.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>总结在LeetCode中遇到的和为定值的一系列题目</p><a id="more"></a><p>在数组中碰到数组和为定值的大致可以分为这两类，一类是这些数不连续，从两个数和为定值到多个数和为定值，最后升级到动态规划的多重部分和问题；另一类是数必须是连续的子数组问题</p><h2 id="两个数和为定值"><a href="#两个数和为定值" class="headerlink" title="两个数和为定值"></a>两个数和为定值</h2><p>这类题应该是最常见的题型了，常见的有两种方法：</p><ol><li>Hash，对每个a[i]，通过hash表快速判断出target-a[i]是否在数列中，这种方法不管数组是有序的还是无序的时间复杂度都是O(n)</li><li>双指针，用两个指针i，j分别指向数组的两端，依次判断<code>a[i] + a[j]</code>与target的大小情况，大于target则j–，小于target则i++，如果数组是有序时间复杂度为O(n)，如果数组不是有序的时间复杂度为O(nlogn)</li></ol><h3 id="LeetCode-1"><a href="#LeetCode-1" class="headerlink" title="LeetCode 1"></a>LeetCode 1</h3><p>题意</p><p>给定有序的一个数组，求其中两个数的和刚好为定值target，返回这两个数的索引值</p><p>代码：<br><figure class="highlight cpp"><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="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; twoSum(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums, <span class="keyword">int</span> target) &#123;</span><br><span class="line">        <span class="built_in">unordered_map</span>&lt;<span class="keyword">int</span>, <span class="keyword">int</span>&gt; m;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; nums.size(); ++i) &#123;</span><br><span class="line">            <span class="keyword">if</span> (m.count(target - nums[i])) &#123;</span><br><span class="line">                <span class="keyword">return</span> &#123;i, m[target - nums[i]]&#125;;</span><br><span class="line">            &#125;</span><br><span class="line">            m[nums[i]] = i;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h2 id="多个数和为定值"><a href="#多个数和为定值" class="headerlink" title="多个数和为定值"></a>多个数和为定值</h2><p>对于求解数组中m个数的和为定值的问题，枚举最开始的一个数都可以转换为m-1个数和为定值的问题，其最优的时间复杂度为O(n^m)。因为m如果大于2，排序的开销就不算在里面了，所以采用双指针的方法更加简单</p><h3 id="LeetCode-3"><a href="#LeetCode-3" class="headerlink" title="LeetCode 3"></a>LeetCode 3</h3><p>题意：</p><p>求数组中3个数的和为定值的这个3个数的索引值</p><p>代码：<br><figure class="highlight cpp"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&gt; threeSum(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums) &#123;</span><br><span class="line">        <span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&gt; res;</span><br><span class="line">        </span><br><span class="line">        sort(nums.begin(),nums.end());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">int</span> len = nums.size();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;len;i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span>(i==<span class="number">0</span> || nums[i]!=nums[i<span class="number">-1</span>]) <span class="comment">// 去重</span></span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">for</span>(<span class="keyword">int</span> j=i+<span class="number">1</span>,k=len<span class="number">-1</span>;j&lt;k;)</span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="keyword">if</span>(nums[i]+nums[j]+nums[k] == <span class="number">0</span>)</span><br><span class="line">                    &#123;</span><br><span class="line">                        <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; tmp = &#123;nums[i],nums[j],nums[k]&#125;;</span><br><span class="line">                        res.push_back(tmp);</span><br><span class="line">                        j++;</span><br><span class="line">                        <span class="keyword">while</span>(j&lt;k &amp;&amp; nums[j]==nums[j<span class="number">-1</span>]) j++;</span><br><span class="line">                        k=len<span class="number">-1</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span>(nums[j]+nums[k]+nums[i]&gt;<span class="number">0</span>)</span><br><span class="line">                    &#123;</span><br><span class="line">                        k--;</span><br><span class="line">                        </span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        j++;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h2 id="多重部分和问题"><a href="#多重部分和问题" class="headerlink" title="多重部分和问题"></a>多重部分和问题</h2><p>进阶可以转换成一个DP的题：有 n 种大小不同的数字 a[i]，每种 m[i] 个，判断是否可以从这些数字中选出若干个使他们的和恰好为 K。<br>设 dp[i+1][j] 为前 i 种数加和为 j 时第 i 种数最多能剩余多少个。（不能得到为-1）</p><p>这样状态转移方程为：<br><img src="http://data3.liuin.cn/2018-09-07-15363320821084.jpg" alt=""></p><p>模板代码：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> a[maxn],m[maxn],dp[maxm];    <span class="comment">//a表示数，m表示数的个数，dp范围是所有数的和的范围</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">fun</span><span class="params">(<span class="keyword">int</span> n,<span class="keyword">int</span> K)</span> <span class="comment">//n表示数字种类,K表示组成的和</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">memset</span>(dp, <span class="number">-1</span>, <span class="keyword">sizeof</span>(dp));</span><br><span class="line">dp[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>; i&lt;n; ++i) &#123;    <span class="comment">//根据存储方式作出改变</span></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>; j&lt;=K; ++j) &#123;</span><br><span class="line"><span class="keyword">if</span>(dp[j] &gt;= <span class="number">0</span>) dp[j] = m[i]; <span class="comment">// 前i-1个数已经能凑成j了</span></span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span>(j &lt; a[i] || dp[j-a[i]] &lt;= <span class="number">0</span>) dp[j] = <span class="number">-1</span>; <span class="comment">// 否则，凑不成j或者a[i]已经用完，则无法满足</span></span><br><span class="line"><span class="keyword">else</span> dp[j] = dp[j-a[i]] - <span class="number">1</span>; <span class="comment">// 否则可以凑成</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> dp[K]&gt;=<span class="number">0</span>;   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="非负数组的子数组和为定值"><a href="#非负数组的子数组和为定值" class="headerlink" title="非负数组的子数组和为定值"></a>非负数组的子数组和为定值</h2><p>这个题应该是比较基础的一道题：因为数组和一定递增的，所以采用滑动窗口的思想，维护滑动窗口的两个指针i和j，如果当前窗口和小于target时j++，如果当前窗口和大于target时i++</p><h2 id="子数组和为定值"><a href="#子数组和为定值" class="headerlink" title="子数组和为定值"></a>子数组和为定值</h2><p>还是遍历一遍数组，使得总体的时间复杂度为O(n)，同时记录从第一个数到当前位置数的和为一张hash表，这个表对应的映射项可以是最早出现这个sum的index（以此来求最长子数组的长度），也可以是对应这个sum出现的次数（对应求满足条件的子数组个数）</p><h3 id="LeetCode-560"><a href="#LeetCode-560" class="headerlink" title="LeetCode 560"></a>LeetCode 560</h3><p>给定一个整数数组和一个整数 k，你需要找到该数组中和为 k 的连续的子数组的个数</p><p>代码：<br><figure class="highlight cpp"><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="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">subarraySum</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums, <span class="keyword">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> len = nums.size();</span><br><span class="line">        <span class="built_in">unordered_map</span>&lt;<span class="keyword">int</span>, <span class="keyword">int</span>&gt; m;</span><br><span class="line">        <span class="keyword">int</span> sum = <span class="number">0</span>, res = <span class="number">0</span>;</span><br><span class="line">        m[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            sum += nums[i];</span><br><span class="line">            <span class="comment">// 加上剩余的值出现的次数</span></span><br><span class="line">            res += m[sum - k];</span><br><span class="line">            m[sum] ++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h3 id="变式"><a href="#变式" class="headerlink" title="变式"></a>变式</h3><p>在上面那道题的基础上改为求满足条件的最长子数组的长度</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">subarraySum</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums, <span class="keyword">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> len = nums.size();</span><br><span class="line">        <span class="built_in">unordered_map</span>&lt;<span class="keyword">int</span>, <span class="keyword">int</span>&gt; m;</span><br><span class="line">        <span class="keyword">int</span> sum = <span class="number">0</span>, res = <span class="number">0</span>;</span><br><span class="line">        m[<span class="number">0</span>] = <span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            sum += nums[i];</span><br><span class="line">            <span class="keyword">if</span> (m.find(sum - k) != m.end())</span><br><span class="line">                res = max(res, i - m[sum - k]);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                m[sum] = i;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="子数组和小于等于定值"><a href="#子数组和小于等于定值" class="headerlink" title="子数组和小于等于定值"></a>子数组和小于等于定值</h2><p>这里用到了两个辅助数组：<code>min_value</code>、<code>min_index</code>：<code>min_value[i]</code>表示以i位置开始往后加的最小累加和；<code>min_index</code>表示<code>min_value</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">arr         5   4   -3  -1</span><br><span class="line">index       0   1   2   3</span><br><span class="line">min_value   5   0   -4  -1</span><br><span class="line">min_index   0   3   3   3</span><br></pre></td></tr></table></figure><p>这两个辅助数组是能够在O(n)时间复杂内计算出来的：倒序遍历，min_value[i] 只需要判断min_value[i+1]的值是不是负数，如果是负数就加上，不是就到本身这里结尾。得到这样一个数组以后我们就可能轻易得到从某一个位置开始和最小的子数组。</p><p>有了这两个辅助数组以后，就可以采用滑动窗口的思想，左右两个指针都不回退，右指针以上面辅助数组进行累加，左指针正常遍历，使得总体的时间复杂度为O(n)</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">subarraySum</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums, <span class="keyword">int</span> k)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> len = nums.size();</span><br><span class="line">        <span class="comment">// 计算两个辅助数组</span></span><br><span class="line">        <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; min_value(k, <span class="number">0</span>), min_index(k, <span class="number">0</span>);</span><br><span class="line">        min_value[len<span class="number">-1</span>] = nums[len<span class="number">-1</span>];</span><br><span class="line">        min_index[len<span class="number">-1</span>] = len<span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = len - <span class="number">2</span>; i &gt;= <span class="number">0</span>; i--)</span><br><span class="line">        &#123;</span><br><span class="line">            min_value[i] = min_value[i+<span class="number">1</span>] &lt; <span class="number">0</span> ? min_value[i+<span class="number">1</span>] + nums[i] : nums[i];</span><br><span class="line">            <span class="keyword">if</span>(min_value[i+<span class="number">1</span>] &lt; <span class="number">0</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                min_value[i] = min_value[i+<span class="number">1</span>] + nums[i];</span><br><span class="line">                min_index[i] = min_index[i+<span class="number">1</span>];</span><br><span class="line">            &#125; </span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">            &#123;</span><br><span class="line">                min_value[i] = nums[i];</span><br><span class="line">                min_index[i] = i;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 滑动窗口求解</span></span><br><span class="line">        <span class="keyword">int</span> l = <span class="number">0</span>, r = <span class="number">0</span>, sum = <span class="number">0</span>, res = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>( ; l &lt; len; l++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">while</span>(sum &lt;= k)</span><br><span class="line">            &#123;</span><br><span class="line">                sum += min_value[r];</span><br><span class="line">                r = min_index[r] + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            res = max(res, r - l);</span><br><span class="line">            sum -= nums[l];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;总结在LeetCode中遇到的和为定值的一系列题目&lt;/p&gt;
    
    </summary>
    
      <category term="算法" scheme="https://www.liuin.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="LeetCode" scheme="https://www.liuin.cn/tags/LeetCode/"/>
    
      <category term="算法总结" scheme="https://www.liuin.cn/tags/%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>分布式消息队列 NSQ 和 Kafka 对比</title>
    <link href="https://www.liuin.cn/2018/07/11/%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97-NSQ-%E5%92%8C-Kafka-%E5%AF%B9%E6%AF%94/"/>
    <id>https://www.liuin.cn/2018/07/11/分布式消息队列-NSQ-和-Kafka-对比/</id>
    <published>2018-07-11T22:25:58.000Z</published>
    <updated>2019-04-01T16:56:10.000Z</updated>
    
    <content type="html"><![CDATA[<p>谈谈分布式消息队列的一些特性，比较两种比较常用的消息队列——NSQ和Kafka</p><a id="more"></a><h2 id="消息队列的作用"><a href="#消息队列的作用" class="headerlink" title="消息队列的作用"></a>消息队列的作用</h2><ol><li>解耦，将一个流程加入一层数据接口拆分成两个部分，上游专注通知，下游专注处理</li><li>缓冲，应对流量的突然上涨变更，消息队列有很好的缓冲削峰作用</li><li>异步，上游发送消息以后可以马上返回，处理工作交给下游进行</li><li>广播，让一个消息被多个下游进行处理</li><li>冗余，保存处理的消息，防止消息处理失败导致的数据丢失</li></ol><h2 id="NSQ"><a href="#NSQ" class="headerlink" title="NSQ"></a>NSQ</h2><h3 id="组件"><a href="#组件" class="headerlink" title="组件"></a>组件</h3><p>NSQ主要包含3个组件：</p><ul><li>nsqd：在服务端运行的守护进程，负责接收，排队，投递消息给客户端。能够独立运行，不过通常是由 nsqlookupd 实例所在集群配置的</li><li>nsqlookup：为守护进程，负责管理拓扑信息并提供发现服务。客户端通过查询 nsqlookupd 来发现指定话题（topic）的生产者，并且 nsqd 节点广播话题（topic）和通道（channel）信息</li><li>nsqadmin：一套WEB UI，用来汇集集群的实时统计，并执行不同的管理任务</li></ul><p><img src="http://data3.liuin.cn/2018-09-07-Xnip2018-09-07_11-17-37.png" alt=""></p><h3 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h3><ol><li>消息默认不可持久化，虽然系统支持消息持久化存储在磁盘中（通过设置 –mem-queue-size 为零），不过默认情况下消息都在内存中</li><li>消息最少会被投递一次，假设成立于 nsqd 节点没有错误</li><li>消息无序，是由重新队列(requeues)，内存和磁盘存储的混合导致的，实际上，节点间不会共享任何信息。它是相对的简单完成疏松队列</li><li>支持无 SPOF 的分布式拓扑，nsqd 和 nsqadmin 有一个节点故障不会影响到整个系统的正常运行</li><li>支持requeue，延迟消费机制</li><li>消息push给消费者</li></ol><h3 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h3><p>单个nsqd可以有多个Topic，每个Topic又可以有多个Channel。Channel能够接收Topic所有消息的副本，从而实现了消息多播分发；而Channel上的每个消息被分发给它的订阅者，从而实现负载均衡，所有这些就组成了一个可以表示各种简单和复杂拓扑结构的强大框架。</p><h2 id="Kafka"><a href="#Kafka" class="headerlink" title="Kafka"></a>Kafka</h2><h3 id="整体架构"><a href="#整体架构" class="headerlink" title="整体架构"></a>整体架构</h3><p><img src="http://data3.liuin.cn/2018-09-07-Xnip2018-09-07_11-47-34.png" alt=""></p><h3 id="角色"><a href="#角色" class="headerlink" title="角色"></a>角色</h3><ul><li>Producer：消息发布者，负责发布消息到Kafka broker</li><li>Consumer：消息消费者，向Kafka broker读取消息的客户端</li><li>Broker：Kafka集群中的一个服务器</li><li>Topic：每条发布到Kafka集群的消息都有一个类别，这个类别被称为Topic。</li><li>Consumer Group：Consumer Group对应NSQ的Channel，一个Consumer Group能够消费一个Topic中的所有消息。</li><li>Partition：Parition是物理上的概念，每个Topic包含一个或多个Partition，消息保证在partition中有序，Consumer可以消费多个partition的消息。</li></ul><blockquote><p>Topic &amp; Partition</p></blockquote><p>Topic在逻辑上可以被认为是一个queue，每条消费都必须指定它的Topic，可以简单理解为必须指明把这条消息放进哪个queue里。为了使得Kafka的吞吐率可以线性提高，物理上把Topic分成一个或多个Partition，每个Partition在物理上对应一个文件夹，该文件夹下存储这个Partition的所有消息和索引文件。</p><blockquote><p>Producer消息路由</p></blockquote><p>Producer发送消息到broker时，会根据Paritition机制选择将其存储到哪一个Partition。如果Partition机制设置合理，所有消息可以均匀分布到不同的Partition里，这样就实现了负载均衡。<br>在发送一条消息时，可以指定这条消息的key，Producer根据这个key和Partition机制来判断应该将这条消息发送到哪个Parition。消息在Partition中是有序的，同时一个Partition短时间内会提供给特定下游消费的Consumer 消费，这样可以提供业务中某些场景的有序保证。</p><blockquote><p>Consumer Group</p></blockquote><p>使用Consumer high level API时，同一Topic的一条消息只能被同一个Consumer Group内的一个Consumer消费，但多个Consumer Group可同时消费这一消息。</p><p>这是Kafka用来实现一个Topic消息的广播（发给所有的Consumer）和单播（发给某一个Consumer）的手段。一个Topic可以对应多个Consumer Group。如果需要实现广播，只要每个Consumer有一个独立的Group就可以了。要实现单播只要所有的Consumer在同一个Group里。用Consumer Group还可以将Consumer进行自由的分组而不需要多次发送消息到不同的Topic。</p><h3 id="特性-1"><a href="#特性-1" class="headerlink" title="特性"></a>特性</h3><ol><li>存储上使用了顺序访问磁盘和零拷贝技术(将磁盘文件的数据复制到页面缓存中一次，然后将数据从页面缓存直接发送到网络中)，使得其具有非常强大的吞吐性能</li><li>数据落磁盘，能够持久化，支持消息的重新消费</li><li>投递保证支持 at least one / at most one / exactly once</li><li>Partition / Comsumer Group内保证消息有序</li></ol><h2 id="对比"><a href="#对比" class="headerlink" title="对比"></a>对比</h2><h3 id="存储"><a href="#存储" class="headerlink" title="存储"></a>存储</h3><ul><li>NSQ 默认是把消息放到内存中，只有当队列里消息的数量超过–mem-queue-size配置的限制时，才会对消息进行持久化。</li><li>Kafka 会把写到磁盘中进行持久化，并通过顺序读写磁盘来保障性能。持久化能够让Kafka做更多的事情：消息的重新消费（重置offset）；让数据更加安全，不那么容易丢失。同时Kafka还通过partition的机制，对消息做了备份，进一步增强了消息的安全性。</li></ul><h3 id="推拉模型"><a href="#推拉模型" class="headerlink" title="推拉模型"></a>推拉模型</h3><ul><li>NSQ 使用的是推模型，推模型能够使得时延非常小，消息到了马上就能够推送给下游消费，但是下游消费能够无法控制，推送过快可能导致下游过载。</li><li>Kafka 使用的拉模型，拉模型能够让消费者自己掌握节奏，但是这样轮询会让整个消费的时延增加，不过消息队列本身对时延的要求不是很大，这一点影响不是很大。</li></ul><h3 id="消息的顺序性"><a href="#消息的顺序性" class="headerlink" title="消息的顺序性"></a>消息的顺序性</h3><ul><li>NSQ 因为不能够把特性消息和消费者对应起来，所以无法实现消息的有序性。</li><li>Kafka 因为消息在Partition中写入是有序的，同时一个Partition只能够被一个Consumer消费，这样就可能实现消息在Partition中的有序。自定义写入哪个Partition的规则能够让需要有序消费的相关消息都进入同一个Partition中被消费，这样达到”全局有序“</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;谈谈分布式消息队列的一些特性，比较两种比较常用的消息队列——NSQ和Kafka&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="消息队列" scheme="https://www.liuin.cn/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"/>
    
  </entry>
  
  <entry>
    <title>Python 多线程与多进程</title>
    <link href="https://www.liuin.cn/2018/07/04/Python-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8E%E5%A4%9A%E8%BF%9B%E7%A8%8B/"/>
    <id>https://www.liuin.cn/2018/07/04/Python-多线程与多进程/</id>
    <published>2018-07-04T16:18:01.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近实习接触到这方面的东西，整理了一下</p><a id="more"></a><h2 id="Python多线程并不是真的多线程"><a href="#Python多线程并不是真的多线程" class="headerlink" title="Python多线程并不是真的多线程"></a>Python多线程并不是真的多线程</h2><p>Python代码的执行由Python虚拟机（解释器）来控制。Python在设计之初就考虑要在主循环中，同时只有一个线程在执行，就像单CPU的系统中运行多个进程那样，内存中可以存放多个程序，但任意时刻，只有一个程序在CPU中运行。同样地，<strong>虽然Python解释器可以运行多个线程，只有一个线程在解释器中运行。对Python虚拟机的访问由全局解释器锁（GIL）来控制，正是这个锁能保证同时只有一个线程在运行</strong>。</p><p>在多线程环境中，Python虚拟机按照以下方式执行。</p><ol><li>设置GIL。</li><li>切换到一个线程去执行。</li><li>运行。</li><li>把线程设置为睡眠状态。</li><li>解锁GIL。</li><li>再次重复以上步骤。</li></ol><p>对所有面向I/O的（会调用内建的操作系统C代码的）程序来说，GIL会在这个I/O调用之前被释放，以允许其他线程在这个线程等待I/O的时候运行。如果某线程并未使用很多I/O操作，它会在自己的时间片内一直占用处理器和GIL。也就是说，<strong>I/O密集型的Python程序比计算密集型的Python程序更能充分利用多线程的好处</strong>。</p><p>我们都知道，比方我有一个4核的CPU，那么这样一来，在单位时间内每个核只能跑一个线程，然后时间片轮转切换。但是Python不一样，它不管你有几个核，单位时间多个核只能跑一个线程，然后时间片轮转。看起来很不可思议？但是这就是GIL搞的鬼。任何Python线程执行前，必须先获得GIL锁，然后，每执行100条字节码，解释器就自动释放GIL锁，让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁，所以，多线程在Python中只能交替执行，即使100个线程跑在100核CPU上，也只能用到1个核。通常我们用的解释器是官方实现的CPython，要真正利用多核，除非重写一个不带GIL的解释器。</p><h2 id="Python-多线程"><a href="#Python-多线程" class="headerlink" title="Python 多线程"></a>Python 多线程</h2><p>Python中有三种模式实现多线程：继承Thread类、Thread对象和multiprocessing.dummy线程池</p><h3 id="继承Thread类"><a href="#继承Thread类" class="headerlink" title="继承Thread类"></a>继承Thread类</h3><p>继承Thread类，通过重写它的run方法实现多线程：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python</span></span><br><span class="line"><span class="comment"># encoding=utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 直接从Thread继承，创建一个新的class，把线程执行的代码放到这个新的 class里</span></span><br><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"> </span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ThreadImpl</span><span class="params">(threading.Thread)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, num)</span>:</span></span><br><span class="line">        threading.Thread.__init__(self)</span><br><span class="line">        self._num = num</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">global</span> total, mutex</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 打印线程名</span></span><br><span class="line">        <span class="keyword">print</span> threading.currentThread().getName()</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">for</span> x <span class="keyword">in</span> xrange(<span class="number">0</span>, int(self._num)):</span><br><span class="line">            <span class="comment"># 取得锁</span></span><br><span class="line">            mutex.acquire()</span><br><span class="line">            total = total + <span class="number">1</span></span><br><span class="line">            <span class="comment"># 释放锁</span></span><br><span class="line">            mutex.release()</span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    <span class="comment">#定义全局变量</span></span><br><span class="line">    <span class="keyword">global</span> total, mutex</span><br><span class="line">    total = <span class="number">0</span></span><br><span class="line">    <span class="comment"># 创建锁</span></span><br><span class="line">    mutex = threading.Lock()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">#定义线程池</span></span><br><span class="line">    threads = []</span><br><span class="line">    <span class="comment"># 创建线程对象</span></span><br><span class="line">    <span class="keyword">for</span> x <span class="keyword">in</span> xrange(<span class="number">0</span>, <span class="number">40</span>):</span><br><span class="line">        threads.append(ThreadImpl(<span class="number">100</span>))</span><br><span class="line">    <span class="comment"># 启动线程</span></span><br><span class="line">    <span class="keyword">for</span> t <span class="keyword">in</span> threads:</span><br><span class="line">        t.start()</span><br><span class="line">    <span class="comment"># 等待子线程结束</span></span><br><span class="line">    <span class="keyword">for</span> t <span class="keyword">in</span> threads:</span><br><span class="line">        t.join()  </span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 打印执行结果</span></span><br><span class="line">    <span class="keyword">print</span> total</span><br></pre></td></tr></table></figure><p>需要注意的是：</p><ul><li>一定要有<code>Thread.__init__(self)</code>这句话</li><li>执行的功能函数必须叫run</li></ul><h3 id="Thread对象"><a href="#Thread对象" class="headerlink" title="Thread对象"></a>Thread对象</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python</span></span><br><span class="line"><span class="comment"># encoding=utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">threadFunc</span><span class="params">(num)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> total, mutex</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 打印线程名</span></span><br><span class="line">    <span class="keyword">print</span> threading.currentThread().getName()</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">for</span> x <span class="keyword">in</span> xrange(<span class="number">0</span>, int(num)):</span><br><span class="line">        <span class="comment"># 取得锁</span></span><br><span class="line">        mutex.acquire()</span><br><span class="line">        total = total + <span class="number">1</span></span><br><span class="line">        <span class="comment"># 释放锁</span></span><br><span class="line">        mutex.release()</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">(num)</span>:</span></span><br><span class="line">    <span class="comment">#定义全局变量</span></span><br><span class="line">    <span class="keyword">global</span> total, mutex</span><br><span class="line">    total = <span class="number">0</span></span><br><span class="line">    <span class="comment"># 创建锁</span></span><br><span class="line">    mutex = threading.Lock()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">#定义线程池</span></span><br><span class="line">    threads = []</span><br><span class="line">    <span class="comment"># 先创建线程对象</span></span><br><span class="line">    <span class="keyword">for</span> x <span class="keyword">in</span> xrange(<span class="number">0</span>, num):</span><br><span class="line">        threads.append(threading.Thread(target=threadFunc, args=(<span class="number">100</span>,)))</span><br><span class="line">    <span class="comment"># 启动所有线程</span></span><br><span class="line">    <span class="keyword">for</span> t <span class="keyword">in</span> threads:</span><br><span class="line">        t.start()</span><br><span class="line">    <span class="comment"># 主线程中等待所有子线程退出</span></span><br><span class="line">    <span class="keyword">for</span> t <span class="keyword">in</span> threads:</span><br><span class="line">        t.join()  </span><br><span class="line">        </span><br><span class="line">    <span class="comment"># 打印执行结果</span></span><br><span class="line">    <span class="keyword">print</span> total</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    <span class="comment"># 创建40个线程</span></span><br><span class="line">    main(<span class="number">40</span>)</span><br></pre></td></tr></table></figure><p>需要注意的是：</p><ul><li>args=是一个tuple，即使没有参数也应该用()</li><li>如果希望子线程异步工作，要设置setDaemon为True；如果希望等待子线程工作结束后主进程再执行，在线程start后join</li></ul><h3 id="multiprocessing-dummy线程池"><a href="#multiprocessing-dummy线程池" class="headerlink" title="multiprocessing.dummy线程池"></a>multiprocessing.dummy线程池</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python</span></span><br><span class="line"><span class="comment"># encoding=utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> multiprocessing</span><br><span class="line"><span class="keyword">from</span> multiprocessing.dummy <span class="keyword">import</span> Pool <span class="keyword">as</span> ThreadPool</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Test</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.pool = ThreadPool(processes=<span class="number">8</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">display</span><span class="params">(self, para)</span>:</span></span><br><span class="line">        print(para)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">multi_work</span><span class="params">(self, trans)</span>:</span></span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            self.pool.map(self.display, trans)</span><br><span class="line">        <span class="keyword">except</span> multiprocessing.TimeoutError:</span><br><span class="line">            print(<span class="string">"pool time out"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">close</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.pool.close()</span><br><span class="line">        self.pool.join()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    test = Test()</span><br><span class="line">    t = range(<span class="number">0</span>, <span class="number">6</span>)</span><br><span class="line">    test.multi_work(t)</span><br><span class="line">    test.close()</span><br></pre></td></tr></table></figure><p>注意：</p><ul><li>pool的join要在close之后执行</li><li>pool.map有可能超时，尽量捕捉这个错误</li></ul><h2 id="Python-多进程"><a href="#Python-多进程" class="headerlink" title="Python 多进程"></a>Python 多进程</h2><p>Python 多进程的实现也有三种：继承自multiprocessing.Process类、multiprocessing.process对象和multiprocessing pool进程池</p><h3 id="继承自multiprocessing-Process类"><a href="#继承自multiprocessing-Process类" class="headerlink" title="继承自multiprocessing.Process类"></a>继承自multiprocessing.Process类</h3><p>这里和多线程第一种实现方式一样</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> multiprocessing</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Producer</span><span class="params">(multiprocessing.Process)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        multiprocessing.Process.__init__(self)</span><br><span class="line">        </span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><h3 id="multiprocessing-process对象"><a href="#multiprocessing-process对象" class="headerlink" title="multiprocessing.process对象"></a>multiprocessing.process对象</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> multiprocessing <span class="keyword">import</span> Process</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="comment"># 子进程要执行的代码</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">run_proc</span><span class="params">(name)</span>:</span></span><br><span class="line">  print(<span class="string">'Run child process %s (%s)...'</span> % (name, os.getpid()))</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__==<span class="string">'__main__'</span>:</span><br><span class="line">  print(<span class="string">'Parent process %s.'</span> % os.getpid())</span><br><span class="line">  p = Process(target=run_proc, args=(<span class="string">'test'</span>,))</span><br><span class="line">  print(<span class="string">'Child process will start.'</span>)</span><br><span class="line">  p.start()</span><br><span class="line">  p.join()</span><br><span class="line">  print(<span class="string">'Child process end.'</span>)</span><br></pre></td></tr></table></figure><h3 id="multiprocessing-pool进程池"><a href="#multiprocessing-pool进程池" class="headerlink" title="multiprocessing pool进程池"></a>multiprocessing pool进程池</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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"><span class="keyword">from</span> multiprocessing <span class="keyword">import</span> Pool</span><br><span class="line"><span class="keyword">import</span> os, time, random</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">long_time_task</span><span class="params">(name)</span>:</span></span><br><span class="line">  print(<span class="string">'Run task %s (%s)...'</span> % (name, os.getpid()))</span><br><span class="line">  start = time.time()</span><br><span class="line">  time.sleep(random.random() * <span class="number">3</span>)</span><br><span class="line">  end = time.time()</span><br><span class="line">  print(<span class="string">'Task %s runs %0.2f seconds.'</span> % (name, (end - start)))</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__==<span class="string">'__main__'</span>:</span><br><span class="line">  print(<span class="string">'Parent process %s.'</span> % os.getpid())</span><br><span class="line">  p = Pool(<span class="number">4</span>)</span><br><span class="line">  <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">5</span>):</span><br><span class="line">    p.apply_async(long_time_task, args=(i,))</span><br><span class="line">  print(<span class="string">'Waiting for all subprocesses done...'</span>)</span><br><span class="line">  p.close()</span><br><span class="line">  p.join()</span><br><span class="line">  print(<span class="string">'All subprocesses done.'</span>)</span><br></pre></td></tr></table></figure><h2 id="Tips"><a href="#Tips" class="headerlink" title="Tips"></a>Tips</h2><ol><li>多线程的线程间消息传递使用Queue.Queue；多进程使用multiprocessing.Queue；进程池必须使用multiprocessing.manager().Queue</li><li>多进程的消息传递可以采取Queue和Pipe两种高级数据结构，其中Queue是用Pipe实现的。Pipe只能支持两个进程的生产消费关系，如果存在多生产或者多消费的场景，只能用Queue。Pipe的效率高一些，但是高得有限，整体来看，多进程的消息传递的效率不高，尽量不要进行消息传递</li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://realpython.com/python-gil/" target="_blank" rel="noopener">https://realpython.com/python-gil/</a></li><li><a href="https://my.oschina.net/cloudcoder/blog/226111" target="_blank" rel="noopener">https://my.oschina.net/cloudcoder/blog/226111</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近实习接触到这方面的东西，整理了一下&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="python" scheme="https://www.liuin.cn/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>LeetCode总结——Minimax算法</title>
    <link href="https://www.liuin.cn/2018/06/30/LeetCode%E6%80%BB%E7%BB%93%E2%80%94%E2%80%94Minimax%E7%AE%97%E6%B3%95/"/>
    <id>https://www.liuin.cn/2018/06/30/LeetCode总结——Minimax算法/</id>
    <published>2018-06-30T22:23:56.000Z</published>
    <updated>2019-01-06T14:12:57.000Z</updated>
    
    <content type="html"><![CDATA[<p>总结在解决博弈问题中会用到的一个算法——Minimax算法</p><a id="more"></a><h2 id="什么是Minimax算法"><a href="#什么是Minimax算法" class="headerlink" title="什么是Minimax算法"></a>什么是Minimax算法</h2><p>什么是Minimax，它是用在决策轮、博弈论和概率论中的一条决策规则。它被用来最小化最坏情况下的可能损失。“最坏”情况是对手带来的最坏情况，“最小”是我要执行的一个最优策略的目标。</p><p>实际使用中一般，DFS来遍历当前局势以后所有可能的结果，通过『最大化』自己和『最小化』对手的方法获取下一步的动作。</p><h2 id="LeetCode-486"><a href="#LeetCode-486" class="headerlink" title="LeetCode 486"></a>LeetCode 486</h2><p>给定一个数组，双方轮流从数组的两边取出一个数，判断最后谁取的数多。</p><p>这是一个博弈问题，站在我的角度一定是要使自己的收益最大，但是站在对方的角度一定是要使我的收益最小。此时我们可以用f[i][j]表示我方在i~j这个数组下的收益，s[i][j]表示对方从两边拿了一个数以后我方的收益。此时不难得出状态转移方程：<code>f[i][j] = max(nums[i] + s[i+1][j], nums[j] + s[i][j-1])</code> 和 <code>min(f[i+1][j], f[i][j-1])</code> </p><p>代码：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// Minimax 算法</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">PredictTheWinner</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> len = nums.size();</span><br><span class="line">        <span class="keyword">if</span>(len == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; &gt; f(len, <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;(len, <span class="number">0</span>)), s(len, <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;(len, <span class="number">0</span>));</span><br><span class="line">        <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i : nums) sum += i;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; len; j++)</span><br><span class="line">        &#123;</span><br><span class="line">            f[j][j] = nums[j];</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">int</span> i = j<span class="number">-1</span>; i &gt;= <span class="number">0</span>; i--)</span><br><span class="line">            &#123;</span><br><span class="line">                f[i][j] = max(nums[i] + s[i+<span class="number">1</span>][j], nums[j] + s[i][j<span class="number">-1</span>]);</span><br><span class="line">                s[i][j] = min(f[i+<span class="number">1</span>][j], f[i][j<span class="number">-1</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> f[<span class="number">0</span>][len<span class="number">-1</span>] &gt;= (sum+<span class="number">1</span>)/<span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h2 id="LeetCode-375"><a href="#LeetCode-375" class="headerlink" title="LeetCode 375"></a>LeetCode 375</h2><p>题意：某人从1~n中选一个数k，你每次给出一个数x，他会告诉你x与n的关系(大于，小于或等于)，每次询问你都需要花费x的代价，问你至少需要花费多少钱才能保证查找到k是多少。</p><p>一道比较典型的Minimax题目，最小化最大值，当确定中间的一个数x的时候，为了保证找到k一定是选取两边的代价中最大的。但是你可以选取这个x时，你可以选取一个代价最小的x。dp[i][j]表示从i到j猜出值所需要的代价，这时我们可以得到状态转移方程：<code>dp[i][j] = min(x + max(dp[i][k-1], dp[k+1][j]) ) {i &lt;= k &lt;= j}</code></p><p>代码：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// Minimax算法，dp思路</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">getMoneyAmount</span><span class="params">(<span class="keyword">int</span> n)</span> </span>&#123;</span><br><span class="line">        <span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; &gt; dp(n + <span class="number">1</span>, <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;(n + <span class="number">1</span>, INT_MAX));</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt;= n; i++)</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">int</span> j = <span class="number">0</span>; j &lt;= n; j++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span>(i == j) dp[i][j] = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">else</span> <span class="keyword">if</span> (i &gt; j) dp[i][j] = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">int</span> j = <span class="number">0</span>; j &lt;= n-i; j++)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">int</span> tem = INT_MAX;</span><br><span class="line">                <span class="keyword">for</span>(<span class="keyword">int</span> k = j; k &lt;= j+i; k++)</span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="keyword">if</span>(k == <span class="number">0</span>)</span><br><span class="line">                        tem = min(tem, k + dp[k+<span class="number">1</span>][j+i]);</span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span>(k == n)</span><br><span class="line">                        tem = min(tem, k + dp[j][k<span class="number">-1</span>]);</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        tem = min(tem, k + max(dp[j][k<span class="number">-1</span>], dp[k+<span class="number">1</span>][j+i]));</span><br><span class="line">                &#125;</span><br><span class="line">                dp[j][j+i] = tem;</span><br><span class="line">            &#125;</span><br><span class="line">        <span class="keyword">return</span> dp[<span class="number">0</span>][n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><h2 id="LeetCode-464"><a href="#LeetCode-464" class="headerlink" title="LeetCode 464"></a>LeetCode 464</h2><p>题意：给定两个数m和target，两人依次从1到m的m个数中取出一个数，当轮到的人取出一个数以后使得所有取出的数不小于target这个人就获胜了，判断第一个取的能不能取得游戏的胜利</p><p>按照Minimax的思路，当我方作出决策的时候一定作出的是最我方损失最小的决策。当我方所在一个状态数组（1到m中各个数的取出状态数组）和一个target的时候，这个时候我们要做的是从这个状态数组中标记一个数为拿出状态，使得其大于target或者使得轮到对方决策后一定是输。</p><p>这里比较棘手的就是这个状态数组了，但是题目给了一个条件，m的值不会超过20个，这个时候我们就可以做一个状态压缩——用status一个数表示整个状态数组：那么这个时候我们就可以得到状态转移方程：<code>dp[n][status] = ((1 &lt;&lt; x) &amp; status) == 0 &amp;&amp; (x &gt;= n || dp[n-x][status | (1 &lt;&lt; x)])</code>，其中<code>((1 &lt;&lt; x) &amp; status)</code>表示当前选择的数x是否被选择过；<code>status | (1 &lt;&lt; x)</code>表示选择了x以后的状态数组的状态</p><p>虽然得到状态转移方程，但是我们不好通过遍历求解，这个时候就可以将动态规划“退化”成递归加上状态记录。这里dp按理说是一个二维数组，但是status和n是有关系的，n表示的是总数减去其取出的数。这里使用map来表示这个dp数组，因为能够表示出三种状态：没有访问过、true、false</p><p>代码：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">int</span> n;</span><br><span class="line">    <span class="built_in">map</span>&lt;<span class="keyword">int</span>, <span class="keyword">bool</span>&gt; dp;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// Minimax算法思想，最小化对手的收益</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">canIWin</span><span class="params">(<span class="keyword">int</span> maxChoosableInteger, <span class="keyword">int</span> desiredTotal)</span> </span>&#123;</span><br><span class="line">        n = maxChoosableInteger;</span><br><span class="line">        <span class="keyword">if</span>(n &gt;= desiredTotal) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span>((<span class="number">1</span> + n) * n / <span class="number">2</span> &lt; desiredTotal) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">return</span> solute(desiredTotal, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 因为status和nowNum是有关联关系的，所以map中需要一个</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">solute</span><span class="params">(<span class="keyword">int</span> nowNum, <span class="keyword">int</span> status)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(dp.find(status) != dp.end()) <span class="keyword">return</span> dp[status];</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= n; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> tem = (<span class="number">1</span> &lt;&lt; i);</span><br><span class="line">            <span class="keyword">if</span>((tem &amp; status) == <span class="number">0</span> &amp;&amp; (i &gt;= nowNum || !solute(nowNum - i, tem | status)))</span><br><span class="line">            &#123;</span><br><span class="line">                dp[status] = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        dp[status] = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;总结在解决博弈问题中会用到的一个算法——Minimax算法&lt;/p&gt;
    
    </summary>
    
      <category term="算法" scheme="https://www.liuin.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="LeetCode" scheme="https://www.liuin.cn/tags/LeetCode/"/>
    
      <category term="算法总结" scheme="https://www.liuin.cn/tags/%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>推荐两个终端代理工具：ProxyChains 和 Proxifier</title>
    <link href="https://www.liuin.cn/2018/06/24/%E6%8E%A8%E8%8D%90%E4%B8%A4%E4%B8%AA%E7%BB%88%E7%AB%AF%E4%BB%A3%E7%90%86%E5%B7%A5%E5%85%B7%EF%BC%9AProxyChains-%E5%92%8C-Proxifier/"/>
    <id>https://www.liuin.cn/2018/06/24/推荐两个终端代理工具：ProxyChains-和-Proxifier/</id>
    <published>2018-06-24T15:10:19.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>Shadowsocks代理是sock5代理，但是我们的终端中的很多应用都是不走sock5代理的，这个时候就需要一些工具来让这些数据通过sock5进行传输，这里推荐两个Linux和Mac中常用的工具：ProxyChains 和 Proxifier</p><a id="more"></a><h2 id="ProxyChains"><a href="#ProxyChains" class="headerlink" title="ProxyChains"></a>ProxyChains</h2><p>ProxyChains的功能就是Hook 了 sockets 相关的操作，让普通程序的 sockets 数据走 SOCKS/HTTP 代理。其在实现部分主要是重写了部分socket函数。</p><p>其能够在同一条代理链中整合不同类型的代理：<br><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">your_host &lt;--&gt;socks5 &lt;--&gt; http &lt;--&gt; socks4 &lt;--&gt; target_host</span><br></pre></td></tr></table></figure></p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><blockquote><p>源码编译安装</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// 下载源码</span><br><span class="line">$ git <span class="built_in">clone</span> https://github.com/rofl0r/proxychains-ng</span><br><span class="line">// 编译</span><br><span class="line">$ ./configure --prefix=/usr --sysconfdir=/etc</span><br><span class="line">$ make</span><br><span class="line">$ make install</span><br><span class="line">$ make install-config (安装proxychains.conf配置文件)</span><br></pre></td></tr></table></figure><blockquote><p>Mac 安装</p></blockquote><p>因为macOS 10.11 后开启了 <a href="https://support.apple.com/zh-cn/ht204899" target="_blank" rel="noopener">SIP（System Integrity Protection）</a> 会导致命令行下 proxychains-ng 代理的模式失效。所以要安装ProxyChains首先需要关闭SIP功能</p><ul><li>部分关闭SIP</li></ul><p>重启Mac，按住Option键进入启动盘选择模式，再按⌘ + R进入Recovery模式。<br>实用工具（Utilities）-&gt; 终端（Terminal）。<br>输入命令<code>csrutil enable --without debug</code>运行。<br>重启进入系统后，终端里输入 csrutil status，结果中如果有 Debugging Restrictions: disabled 则说明关闭成功。</p><ul><li>全部关闭SIP</li></ul><p>重启Mac，按住Option键进入启动盘选择模式，再按⌘ + R进入Recovery模式。<br>实用工具（Utilities）-&gt; 终端（Terminal）。<br>输入命令<code>csrutil disable</code>运行。<br>重启进入系统后，终端里输入 csrutil status，结果中如果有 System Integrity Protection status:disabled. 则说明关闭成功。</p><p>关闭以后通过<code>brew</code>进行安装就行了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew install proxychains-ng</span><br></pre></td></tr></table></figure><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>proxychains-ng默认配置文件名为proxychains.conf</p><ul><li>通过源代码编译安装的默认为/etc/proxychains.conf。</li><li>Mac下用<code>Homebrew</code>安装的默认为/usr/local/etc/proxychains.conf</li></ul><p>配置只需要将代理加入[ProxyList]中：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[ProxyList]</span><br><span class="line">socks5  127.0.0.1 1086</span><br></pre></td></tr></table></figure><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p>在你需要进行代理的页面前面加上<code>proxychains4</code>即可</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ proxhchains4 curl www.google.com</span><br></pre></td></tr></table></figure><h2 id="Proxifier"><a href="#Proxifier" class="headerlink" title="Proxifier"></a>Proxifier</h2><p>Mac用户可能会觉得关闭SIP会造成一些安全隐患，这个时候可以使用Mac下的一个工具：Proxifier</p><p>Proxifier可以设定Mac上不同的应用走不同的代理，我们把我们平常需要的一些终端应用设置走指定的代理就行了</p><h3 id="添加代理"><a href="#添加代理" class="headerlink" title="添加代理"></a>添加代理</h3><p>打开Proxifier，打开<code>Proxies-&gt;Add</code>，输入地址和端口号添加对应的sock5代理</p><p><img src="http://data3.liuin.cn/2018-06-24-15298257914991.jpg" alt=""></p><h3 id="设置代理规则"><a href="#设置代理规则" class="headerlink" title="设置代理规则"></a>设置代理规则</h3><p>在<code>Rules</code>模块中，我们可以设置指定应用、目标主机、目标端口走我们刚才添加的代理</p><p><img src="http://data3.liuin.cn/2018-06-24-15298261213304.jpg" alt=""></p><p>需要注意的是，给我们提供的代理的Shadowsocks要设置成直接连接不能加入代理中，否则会造成整个代理链成了一条环，最后上不了网。</p><p>设置以后就可能在终端中享受代理服务了~</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Shadowsocks代理是sock5代理，但是我们的终端中的很多应用都是不走sock5代理的，这个时候就需要一些工具来让这些数据通过sock5进行传输，这里推荐两个Linux和Mac中常用的工具：ProxyChains 和 Proxifier&lt;/p&gt;
    
    </summary>
    
      <category term="软件使用" scheme="https://www.liuin.cn/categories/%E8%BD%AF%E4%BB%B6%E4%BD%BF%E7%94%A8/"/>
    
    
      <category term="效率提升" scheme="https://www.liuin.cn/tags/%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87/"/>
    
  </entry>
  
  <entry>
    <title>初识 Go 语言</title>
    <link href="https://www.liuin.cn/2018/06/22/%E5%88%9D%E8%AF%86-Go-%E8%AF%AD%E8%A8%80/"/>
    <id>https://www.liuin.cn/2018/06/22/初识-Go-语言/</id>
    <published>2018-06-22T08:51:45.000Z</published>
    <updated>2019-01-06T14:12:57.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天来会一会这个小地鼠</p><a id="more"></a><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Go是Google开发的一种静态强类型、编译型、并发型，并具有垃圾回收功能的编程语言。Go语言号称集多数编程语言的优势于一身，具有较高的生产效率、先进的依赖管理和类型系统，以及原生的并发计算支持。</p><p>Go语言的语法接近C语言，但对于变量的声明有所不同。Go语言支持垃圾回收功能。Go语言的并行模型是以东尼·霍尔的通信顺序进程（CSP）为基础，采取类似模型的其他语言包括Occam和Limbo，但它也具有Pi运算的特征，比如通道传输。</p><p>与C++相比，Go语言并不包括如异常处理、继承、泛型、断言、虚函数等功能，但增加了 Slice 型、并发、管道、垃圾回收、接口（Interface）等特性的语言级支持</p><h2 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h2><ol><li><p>部署简单。Go 是一个编译型语言，Go 编译生成的是一个静态可执行文件，除了 glibc 外没有其他外部依赖。</p></li><li><p>并发性好。Goroutine 和 channel 使得编写高并发的服务端软件变得相当容易，很多情况下完全不需要考虑锁机制以及由此带来的问题。</p></li><li><p>代码风格强制统一</p></li><li><p>Go语言语法趋于脚本化，比较简洁，但Go是编译型语言而非解释型语言。</p></li><li><p>Go语言使用垃圾自动回收机制（GC），GC是定时自动启动，人工可做稍微的干预。</p></li></ol><h2 id="劣势"><a href="#劣势" class="headerlink" title="劣势"></a>劣势</h2><ol><li>错误处理</li></ol><p>在Go语言中处理错误的基本模式是：函数通常返回多个值，其中最后一个值是error类型，用于表示错误类型极其描述；调用者每次调用完一个函数，都需要检查这个error并进行相应的错误处理：if err != nil { /*这种代码写多了不想吐么*/ }。此模式跟C语言那种很原始的错误处理相比如出一辙，并无实质性改进。</p><ol start="2"><li>软件包管理</li></ol><p>Go 语言的软件包管理绝对不是完美的。默认情况下，它没有办法制定特定版本的依赖库，也无法创建可复写的 builds。相比之下 Python、Node 和 Ruby 都有更好的软件包管理系统。然而通过正确的工具，Go 语言的软件包管理也可以表现得不错。</p><h2 id="基本语法学习"><a href="#基本语法学习" class="headerlink" title="基本语法学习"></a>基本语法学习</h2><p>找到两个口碑比较好的入门Go语言的教程：</p><ul><li><a href="https://tour.go-zh.org/welcome/1" target="_blank" rel="noopener">Go语言之旅</a></li><li><a href="https://legacy.gitbook.com/book/yar999/gopl-zh/details" target="_blank" rel="noopener">Go语言圣经</a></li></ul><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>Go在Linux上的配置比较简单，无非就是下载一个二进制文件，然后添加一下环境变量。</p><p>Go在mac上推荐使用brew进行安装，使用官网的安装包进行安装因为<a href="https://support.apple.com/zh-cn/HT204899" target="_blank" rel="noopener">苹果对一些目录的保护</a>，后面在安装其他库的时候可能会存在问题</p><p>具体可以参考官方<a href="https://golang.org/doc/install" target="_blank" rel="noopener">配置文档</a></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.zhihu.com/question/21409296" target="_blank" rel="noopener">https://www.zhihu.com/question/21409296</a></li><li><a href="https://blog.csdn.net/itsenlin/article/details/53750262" target="_blank" rel="noopener">https://blog.csdn.net/itsenlin/article/details/53750262</a></li><li><a href="https://blog.csdn.net/liigo/article/details/23699459" target="_blank" rel="noopener">https://blog.csdn.net/liigo/article/details/23699459</a></li><li><a href="http://www.techug.com/post/bad-and-good-of-golang.html" target="_blank" rel="noopener">http://www.techug.com/post/bad-and-good-of-golang.html</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;今天来会一会这个小地鼠&lt;/p&gt;
    
    </summary>
    
      <category term="开发随笔" scheme="https://www.liuin.cn/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="Go" scheme="https://www.liuin.cn/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Redis源码剖析——客户端和服务器</title>
    <link href="https://www.liuin.cn/2018/06/19/Redis%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90%E2%80%94%E2%80%94%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%92%8C%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    <id>https://www.liuin.cn/2018/06/19/Redis源码剖析——客户端和服务器/</id>
    <published>2018-06-19T14:52:27.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>Redis服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接。这篇文章将通过源码看看客户端和服务器的底层数据结构和工作过程</p><a id="more"></a><p>在Redis这种一对多的服务模式下，每个客户端可以向服务器发送命令请求,而服务器则接收并处理客户端发送的命令请求,并向客户端返回命令回复。通过使用由I/O多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式来处理命令请求,并与多个客户端进行网络通信。</p><h2 id="客户端"><a href="#客户端" class="headerlink" title="客户端"></a>客户端</h2><h3 id="客户端数据结构"><a href="#客户端数据结构" class="headerlink" title="客户端数据结构"></a>客户端数据结构</h3><p>客户端底层的数据结构如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">redisClient</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint64_t</span> id;            <span class="comment">/* Client incremental unique ID. */</span></span><br><span class="line">    <span class="comment">// 套接字描述符</span></span><br><span class="line">    <span class="keyword">int</span> fd;</span><br><span class="line">    redisDb *db;</span><br><span class="line">    <span class="keyword">int</span> dictid;</span><br><span class="line">    <span class="comment">// 客户端名字</span></span><br><span class="line">    robj *name;             <span class="comment">/* As set by CLIENT SETNAME */</span></span><br><span class="line">    <span class="comment">// 输入缓冲区，保存客户端发送的命令请求</span></span><br><span class="line">    sds querybuf;</span><br><span class="line">    <span class="keyword">size_t</span> querybuf_peak;   <span class="comment">/* Recent (100ms or more) peak of querybuf size */</span></span><br><span class="line">    <span class="comment">// 命令和命令参数</span></span><br><span class="line">    <span class="keyword">int</span> argc;</span><br><span class="line">    robj **argv;</span><br><span class="line">    <span class="comment">// 命令实现函数字典</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">redisCommand</span> *<span class="title">cmd</span>, *<span class="title">lastcmd</span>;</span></span><br><span class="line">    <span class="keyword">int</span> reqtype;</span><br><span class="line">    <span class="keyword">int</span> multibulklen;       <span class="comment">/* number of multi bulk arguments left to read */</span></span><br><span class="line">    <span class="keyword">long</span> bulklen;           <span class="comment">/* length of bulk argument in multi bulk request */</span></span><br><span class="line">    <span class="built_in">list</span> *reply;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> reply_bytes; <span class="comment">/* Tot bytes of objects in reply list */</span></span><br><span class="line">    <span class="keyword">int</span> sentlen;            <span class="comment">/* Amount of bytes already sent in the current</span></span><br><span class="line"><span class="comment">                               buffer or object being sent. */</span></span><br><span class="line">    <span class="comment">// 创建客户端时间</span></span><br><span class="line">    <span class="keyword">time_t</span> ctime;           <span class="comment">/* Client creation time */</span></span><br><span class="line">    <span class="comment">// 客户端和服务器最后一次进行互动的时间</span></span><br><span class="line">    <span class="keyword">time_t</span> lastinteraction; <span class="comment">/* time of the last interaction, used for timeout */</span></span><br><span class="line">    <span class="keyword">time_t</span> obuf_soft_limit_reached_time;</span><br><span class="line">    <span class="comment">// 标志，记录客户端的角色</span></span><br><span class="line">    <span class="keyword">int</span> flags;              <span class="comment">/* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */</span></span><br><span class="line">    <span class="comment">// 标志是否通过身份验证</span></span><br><span class="line">    <span class="keyword">int</span> authenticated;      <span class="comment">/* when requirepass is non-NULL */</span></span><br><span class="line">    ... <span class="comment">// 其他相关属性</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Response buffer */</span></span><br><span class="line">    <span class="comment">// 回应缓冲区</span></span><br><span class="line">    <span class="keyword">int</span> bufpos;</span><br><span class="line">    <span class="keyword">char</span> buf[REDIS_REPLY_CHUNK_BYTES];</span><br><span class="line">&#125; redisClient;</span><br></pre></td></tr></table></figure><p>在客户端的各个属性中：</p><p>fd表示套接字描述符，伪客户端的fd属性的值为-1:伪客户端处理的命令请求来源于AOF文件或者Lua脚本,而不是网络,所以这种客户端不需要套接字连接；普通客户端的fd属性的值为大于-1的整数</p><p>命令和命令参数是对输入缓冲的命令进行解析以后获得命令和参数。</p><p><code>cmd</code>是命令的实现函数的数组，命令实现函数的结构如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">redisCommand</span> &#123;</span></span><br><span class="line">    <span class="comment">// 命令名称</span></span><br><span class="line">    <span class="keyword">char</span> *name;</span><br><span class="line">    <span class="comment">// 命令执行函数</span></span><br><span class="line">    redisCommandProc *proc;</span><br><span class="line">    <span class="comment">// 参数个数</span></span><br><span class="line">    <span class="keyword">int</span> arity;</span><br><span class="line">    <span class="comment">// 字符串表示flag</span></span><br><span class="line">    <span class="keyword">char</span> *sflags; <span class="comment">/* Flags as string representation, one char per flag. */</span></span><br><span class="line">    <span class="comment">// 实际flag</span></span><br><span class="line">    <span class="keyword">int</span> flags;    <span class="comment">/* The actual flags, obtained from the 'sflags' field. */</span></span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 指定哪些参数是key</span></span><br><span class="line">    <span class="keyword">int</span> firstkey; <span class="comment">/* The first argument that's a key (0 = no keys) */</span></span><br><span class="line">    <span class="keyword">int</span> lastkey;  <span class="comment">/* The last argument that's a key */</span></span><br><span class="line">    <span class="keyword">int</span> keystep;  <span class="comment">/* The step between first and last key */</span></span><br><span class="line">    <span class="comment">// 统计信息</span></span><br><span class="line">    <span class="keyword">long</span> <span class="keyword">long</span> microseconds, calls;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="客户端的创建和关闭"><a href="#客户端的创建和关闭" class="headerlink" title="客户端的创建和关闭"></a>客户端的创建和关闭</h3><p>当客户端向服务器发出connect请求的时候，服务器的事件处理器就会对这个事件进行处理，创建相应的客户端状态，并将这个新的客户端状态添加到服务器状态结构clients链表的末尾</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 创建一个新客户端</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function">redisClient *<span class="title">createClient</span><span class="params">(<span class="keyword">int</span> fd)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 分配空间</span></span><br><span class="line">    redisClient *c = zmalloc(<span class="keyword">sizeof</span>(redisClient));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当 fd 不为 -1 时，创建带网络连接的客户端</span></span><br><span class="line">    <span class="comment">// 如果 fd 为 -1 ，那么创建无网络连接的伪客户端</span></span><br><span class="line">    <span class="comment">// 因为 Redis 的命令必须在客户端的上下文中使用，所以在执行 Lua 环境中的命令时</span></span><br><span class="line">    <span class="comment">// 需要用到这种伪终端</span></span><br><span class="line">    <span class="keyword">if</span> (fd != <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="comment">// 非阻塞</span></span><br><span class="line">        anetNonBlock(<span class="literal">NULL</span>,fd);</span><br><span class="line">        <span class="comment">// 禁用 Nagle 算法</span></span><br><span class="line">        anetEnableTcpNoDelay(<span class="literal">NULL</span>,fd);</span><br><span class="line">        <span class="comment">// 设置 keep alive</span></span><br><span class="line">        <span class="keyword">if</span> (server.tcpkeepalive)</span><br><span class="line">            anetKeepAlive(<span class="literal">NULL</span>,fd,server.tcpkeepalive);</span><br><span class="line">        <span class="comment">// 绑定读事件到事件 loop （开始接收命令请求）</span></span><br><span class="line">        <span class="keyword">if</span> (aeCreateFileEvent(server.el,fd,AE_READABLE,</span><br><span class="line">            readQueryFromClient, c) == AE_ERR)</span><br><span class="line">        &#123;</span><br><span class="line">            close(fd);</span><br><span class="line">            zfree(c);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化各个属性</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 默认数据库</span></span><br><span class="line">    selectDb(c,<span class="number">0</span>);</span><br><span class="line">    <span class="comment">// 套接字</span></span><br><span class="line">    c-&gt;fd = fd;</span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    listSetFreeMethod(c-&gt;pubsub_patterns,decrRefCountVoid);</span><br><span class="line">    listSetMatchMethod(c-&gt;pubsub_patterns,listMatchObjects);</span><br><span class="line">    <span class="comment">// 如果不是伪客户端，那么添加到服务器的客户端链表中</span></span><br><span class="line">    <span class="keyword">if</span> (fd != <span class="number">-1</span>) listAddNodeTail(server.clients,c);</span><br><span class="line">    <span class="comment">// 初始化客户端的事务状态</span></span><br><span class="line">    initClientMultiState(c);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回客户端</span></span><br><span class="line">    <span class="keyword">return</span> c;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于客户端的启动程序，其大致的逻辑是：读取本地配置，连接服务器获取服务器的配置，获取本地输入的命令并发送到服务器</p><p>一个普通客户端可以因为多种原因而被关闭:</p><ul><li>如果客户端进程退出或者被杀死,那么客户端与服务器之间的网络连接将被关闭,从而造成客户端被关闭。</li><li>如果客户端向服务器发送了带有不符合协议格式的命令请求,那么这个客户端也会被服务器关闭。</li><li>如果客户端成为了CLIENT KLLL命令的目标,那么它也会被关闭。</li></ul><p>关闭客户端的底层实现：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 释放客户端</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">freeClient</span><span class="params">(redisClient *c)</span> </span>&#123;</span><br><span class="line">    listNode *ln;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* Free the query buffer */</span></span><br><span class="line">    sdsfree(c-&gt;querybuf);</span><br><span class="line">    c-&gt;querybuf = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Deallocate structures used to block on blocking ops. */</span></span><br><span class="line">    <span class="keyword">if</span> (c-&gt;flags &amp; REDIS_BLOCKED) unblockClient(c);</span><br><span class="line">    dictRelease(c-&gt;bpop.keys);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* UNWATCH all the keys */</span></span><br><span class="line">    <span class="comment">// 清空 WATCH 信息</span></span><br><span class="line">    unwatchAllKeys(c);</span><br><span class="line">    listRelease(c-&gt;watched_keys);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Unsubscribe from all the pubsub channels */</span></span><br><span class="line">    <span class="comment">// 退订所有频道和模式</span></span><br><span class="line">    pubsubUnsubscribeAllChannels(c,<span class="number">0</span>);</span><br><span class="line">    pubsubUnsubscribeAllPatterns(c,<span class="number">0</span>);</span><br><span class="line">    dictRelease(c-&gt;pubsub_channels);</span><br><span class="line">    listRelease(c-&gt;pubsub_patterns);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Close socket, unregister events, and remove list of replies and</span></span><br><span class="line"><span class="comment">     * accumulated arguments. */</span></span><br><span class="line">    <span class="comment">// 关闭套接字，并从事件处理器中删除该套接字的事件</span></span><br><span class="line">    <span class="keyword">if</span> (c-&gt;fd != <span class="number">-1</span>) &#123;</span><br><span class="line">        aeDeleteFileEvent(server.el,c-&gt;fd,AE_READABLE);</span><br><span class="line">        aeDeleteFileEvent(server.el,c-&gt;fd,AE_WRITABLE);</span><br><span class="line">        close(c-&gt;fd);</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">    listRelease(c-&gt;reply);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 清空命令参数</span></span><br><span class="line">    freeClientArgv(c);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Remove from the list of clients */</span></span><br><span class="line">    <span class="comment">// 从服务器的客户端链表中删除自身</span></span><br><span class="line">    <span class="keyword">if</span> (c-&gt;fd != <span class="number">-1</span>) &#123;</span><br><span class="line">        ln = listSearchKey(server.clients,c);</span><br><span class="line">        redisAssert(ln != <span class="literal">NULL</span>);</span><br><span class="line">        listDelNode(server.clients,ln);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 删除客户端的阻塞信息</span></span><br><span class="line">    <span class="keyword">if</span> (c-&gt;flags &amp; REDIS_UNBLOCKED) &#123;</span><br><span class="line">        ln = listSearchKey(server.unblocked_clients,c);</span><br><span class="line">        redisAssert(ln != <span class="literal">NULL</span>);</span><br><span class="line">        listDelNode(server.unblocked_clients,ln);</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">    <span class="keyword">if</span> (c-&gt;name) decrRefCount(c-&gt;name);</span><br><span class="line">    <span class="comment">// 清除参数空间</span></span><br><span class="line">    zfree(c-&gt;argv);</span><br><span class="line">    <span class="comment">// 清除事务状态信息</span></span><br><span class="line">    freeClientMultiState(c);</span><br><span class="line">    sdsfree(c-&gt;peerid);</span><br><span class="line">    <span class="comment">// 释放客户端 redisClient 结构本身</span></span><br><span class="line">    zfree(c);</span><br><span class="line">&#125;</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>从客户端输入一条指令到服务端完成命令的内容并返回要经历以下这些步骤：</p><ol><li>发送命令请求，Redis服务器的命令请求来自 Redis客户端,当用户在客户端中键人一个命令请求时,客户端会将这个命令请求转换成协议格式,然后通过套接字发送给服务器</li><li>读取命令的内容，服务器接受到套接字以后会产生一个文件事件，通过对文件事件的处理，判断为命令内容</li><li>查找命令实现，根据客户端的命令参数argv[0]，在服务器的命令表中查找指定的命令，并将找到的命令保存到客户端状态的cmd属性里面</li><li>执行预备操作，在执行命令前需要进行一些操作：检查给出的命令是否有效（cmd是否为NULL）;判断给定的参数是否正确；判断客户端是否通过验证</li><li>调用命令的实现函数</li><li>执行后续的工作，包括添加日志，计算时间属性，进行AOF操作等等</li><li>将命令回复发送给客户端</li><li>客户端收到并打印命令</li></ol><h3 id="serverCron函数"><a href="#serverCron函数" class="headerlink" title="serverCron函数"></a>serverCron函数</h3><p>Redis服务器中的 serverCron函数默认每隔100毫秒执行一次,这个函数负责管理服务器的资源,并保持服务器自身的良好运转</p><p>因为serverCron的实现代码太过冗长，所以这里就简单说一些serverCron函数都干了哪些事情</p><ol><li>更新服务器时间缓存</li></ol><p>Redis服务器中许多的操作都需要用到当前的系统时间属性<code>unixtime</code>，serverCron会更新这个时间属性</p><ol start="2"><li>更新LRU时钟</li></ol><p>Reids服务器中实现过期键的删除需要计算其空转时间，计算空转时间需要用LRU时钟，serverCron会更新这个时钟保证Redis过期键删除功能的正常使用</p><ol start="3"><li>更新服务器内存峰值记录</li></ol><p>Redis中使用了一个属性<code>stat_peak_memory</code>记录了使用内存的峰值，这个属性需要serverCron进行更新</p><ol start="4"><li>处理SIGTERM信号</li></ol><p>在启动服务器时, Redis会为服务器进程的 SIGTERM信号关联处理器 sigtermhandier函数,这个信号处理器负责在服务器接到 SIGTERM信号时,打开服务器状态的 shutdown_asap标识。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Redis服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接。这篇文章将通过源码看看客户端和服务器的底层数据结构和工作过程&lt;/p&gt;
    
    </summary>
    
      <category term="源码剖析" scheme="https://www.liuin.cn/categories/%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
      <category term="Redis" scheme="https://www.liuin.cn/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>LeetCode总结——字符串相关算法</title>
    <link href="https://www.liuin.cn/2018/06/18/LeetCode%E6%80%BB%E7%BB%93%E2%80%94%E2%80%94%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%85%B3%E7%AE%97%E6%B3%95/"/>
    <id>https://www.liuin.cn/2018/06/18/LeetCode总结——字符串相关算法/</id>
    <published>2018-06-18T12:37:11.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>总结在LeetCode中字符串相关的常用的一些算法</p><a id="more"></a><h2 id="KMP-算法"><a href="#KMP-算法" class="headerlink" title="KMP 算法"></a>KMP 算法</h2><p>KMP算法解决的是两个字符串的匹配问题（一个字符串是不是另外一个字符串的子串）</p><p>暴力法所需要的时间复杂度是O(n*m)，KMP算法能够优化到O(n)。KMP算法的核心是使用一个next数组实现匹配的加速</p><h3 id="next数组"><a href="#next数组" class="headerlink" title="next数组"></a>next数组</h3><p>最长前缀后缀：一个字符串中所有的前缀和其所有的后缀中最长的相等的长度，比如说“abcabc”的最长前缀后缀为”abc”</p><p>给定一个串s的next数组next[]，其每一位next[i]表示串s[0…i-1]中最长前缀后缀</p><p>使用next数据对字符串匹配进行加速：s1中查找是否有子串s2，如果s1[i]匹配到s2[j]的时候不相等，并且此时next[j]=k，此时不需要重新回到s1[1]继续进行匹配，而是用s2[k]继续和s1[i]进行匹配，依次类推</p><p><img src="http://data3.liuin.cn/2018-06-17-15292350043789.jpg" alt=""></p><p>因为next数组是找到了最长前缀后缀的，所以其能够从最长前缀的匹配跳到最长后缀的匹配，因为中间不可能出现匹配的情况，如果出现匹配那么表示当前next计算的不可能是”最长“前缀后缀。</p><h3 id="next数组求法"><a href="#next数组求法" class="headerlink" title="next数组求法"></a>next数组求法</h3><p>规定<code>next[0] = -1</code>， 因为其前面没有字符串；<code>next[1] = 0</code>，因为其前面的字符串中只有一个字符，后面的next值的计算取决于前面的next值：</p><p>判断当前位置的字符和最长前缀的后一个字符是否相等，如果相等则<code>next[i] = next[i-1] + 1</code>，如果不等再判断最长前缀的最长前缀和其相等不相等，如果还不相等就继续找最长前缀，直到最长前缀长度为0的时候表示没有找到，这个时候<code>next[i] = 0</code></p><h3 id="LeetCode-028"><a href="#LeetCode-028" class="headerlink" title="LeetCode 028"></a>LeetCode 028</h3><p><a href="https://leetcode.com/problems/implement-strstr/" target="_blank" rel="noopener">Implement strStr()</a></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">strStr</span><span class="params">(<span class="built_in">string</span> s, <span class="built_in">string</span> p)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> len = p.size();</span><br><span class="line">        <span class="keyword">if</span>(len == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 生成next数组</span></span><br><span class="line">        <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; next(len, <span class="number">0</span>);</span><br><span class="line">        getNext(next, p);</span><br><span class="line">        <span class="comment">// 两个数组的遍历指针</span></span><br><span class="line">        <span class="keyword">int</span> i = <span class="number">0</span>, j = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span>(i &lt; s.size() &amp;&amp; j &lt; len)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span>(s[i] == p[j])</span><br><span class="line">            &#123;</span><br><span class="line">                i++;</span><br><span class="line">                j++;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">if</span>(j == <span class="number">0</span>) i++;</span><br><span class="line">                <span class="comment">// 根据next数组中的信息进行重新指向</span></span><br><span class="line">                <span class="keyword">else</span> j = next[j];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> j == len ? i - j : <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">getNext</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; &amp;next, <span class="built_in">string</span> p)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        next[<span class="number">0</span>] = <span class="number">-1</span>;</span><br><span class="line">        <span class="comment">// k表示最长前缀后缀的长度</span></span><br><span class="line">        <span class="keyword">int</span> k = <span class="number">-1</span>, i = <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 计算到最后一位</span></span><br><span class="line">        <span class="keyword">while</span>(i &lt; p.size() - <span class="number">1</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// 相匹配的时候对next数组赋值</span></span><br><span class="line">            <span class="keyword">if</span>(k == <span class="number">-1</span> || p[i] == p[k])</span><br><span class="line">                next[++i] = ++k;</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                k = next[k];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="Manacher-算法"><a href="#Manacher-算法" class="headerlink" title="Manacher 算法"></a>Manacher 算法</h2><p>Manacher 算法解决的是字符串中最长的回文子串的问题</p><p>暴力法（技巧：中间添加辅助字符）解决这个问题的时间复杂度为O(n^2)，Manacher算法能够时间复杂度优化为O(n)</p><h3 id="几个概念"><a href="#几个概念" class="headerlink" title="几个概念"></a>几个概念</h3><ul><li>回文半径数组，以i为中心的的回文的半径长度</li><li>回文右边界，遍历过的回文能够达到的最右的下标</li><li>右边界中心位置，回文右边界对应的回文中心点下标</li></ul><h3 id="算法过程"><a href="#算法过程" class="headerlink" title="算法过程"></a>算法过程</h3><p>和暴力解法一样从左向右扩充判断，有以下几种情况：</p><ol><li>当前位置不在回文右边界里面，暴力扩充</li><li>当前位置在回文右边界里面，其关于右边界中心的对称点的回文区域<strong>在左边界里面</strong>，其回文半径和其对称点一样</li><li>当前位置在回文右边界里面，其关于右边界中心的对称点的回文左区域<strong>超过回文左边界</strong>，其回文半径为r-i</li><li>当前位置在回文右边界里面，其关于右边界中心的对称点的回文左区域<strong>与回文左边界重合（压线）</strong>，其回文半径需要在r-i的基础上往后判断</li></ol><p><img src="http://data3.liuin.cn/2018-06-17-15292498856933.jpg" alt=""></p><h3 id="LeetCode-005"><a href="#LeetCode-005" class="headerlink" title="LeetCode 005"></a>LeetCode 005</h3><p><a href="https://leetcode.com/problems/longest-palindromic-substring/description/" target="_blank" rel="noopener">Longest Palindromic Substring</a></p><figure class="highlight cpp"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// Manacher 算法</span></span><br><span class="line">    <span class="function"><span class="built_in">string</span> <span class="title">longestPalindrome</span><span class="params">(<span class="built_in">string</span> s)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 添加辅助字符#</span></span><br><span class="line">        <span class="built_in">string</span> new_s;</span><br><span class="line">        new_s.push_back(<span class="string">'#'</span>);</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; s.size(); i++)</span><br><span class="line">        &#123;</span><br><span class="line">            new_s.push_back(s[i]);</span><br><span class="line">            new_s.push_back(<span class="string">'#'</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        s = new_s;</span><br><span class="line">        <span class="keyword">int</span> len = s.size();</span><br><span class="line">        <span class="keyword">if</span>(len == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 回文半径数组</span></span><br><span class="line">        <span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; pArr(len, <span class="number">0</span>);</span><br><span class="line">        <span class="comment">// C表示回文中心， R表示回文右边界</span></span><br><span class="line">        <span class="keyword">int</span> C = <span class="number">-1</span>, R = <span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">int</span> maxv = INT_MIN, maxi = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// 在回文右边界里面与否的区分</span></span><br><span class="line">            <span class="comment">// 此时pArr表示的是起码不用验的区域 </span></span><br><span class="line">            pArr[i] = R &gt; i ? min(pArr[<span class="number">2</span>*C - i], R - i) : <span class="number">1</span>;</span><br><span class="line">            <span class="comment">// 区域没有越界</span></span><br><span class="line">            <span class="keyword">while</span>(i + pArr[i] &lt; len &amp;&amp; i - pArr[i] &gt; <span class="number">-1</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="comment">// 情况1+4 扩充</span></span><br><span class="line">                <span class="keyword">if</span>(s[i + pArr[i]] == s[i - pArr[i]])</span><br><span class="line">                    pArr[i]++;</span><br><span class="line">                <span class="comment">// 情况2+3</span></span><br><span class="line">                <span class="keyword">else</span></span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(i + pArr[i] &gt; R)</span><br><span class="line">            &#123;</span><br><span class="line">                R = i + pArr[i];</span><br><span class="line">                C = i;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(maxv &lt; pArr[i])</span><br><span class="line">            &#123;</span><br><span class="line">                maxv = pArr[i];</span><br><span class="line">                maxi = i;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">string</span> res;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = maxi - maxv + <span class="number">1</span>; i &lt;= maxi + maxv - <span class="number">1</span>; i++)</span><br><span class="line">            <span class="keyword">if</span>(s[i] != <span class="string">'#'</span>) res.push_back(s[i]);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;总结在LeetCode中字符串相关的常用的一些算法&lt;/p&gt;
    
    </summary>
    
      <category term="算法" scheme="https://www.liuin.cn/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="LeetCode" scheme="https://www.liuin.cn/tags/LeetCode/"/>
    
      <category term="算法总结" scheme="https://www.liuin.cn/tags/%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>《大型网站技术架构》 笔记</title>
    <link href="https://www.liuin.cn/2018/06/18/%E3%80%8A%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84%E3%80%8B-%E7%AC%94%E8%AE%B0/"/>
    <id>https://www.liuin.cn/2018/06/18/《大型网站技术架构》-笔记/</id>
    <published>2018-06-18T10:01:58.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近用了三天的时间把《大型网站技术架构》看完了，收获颇多。这本书主要讲了一个网站从小到大发展过程在技术架构上的需要注意的地方，这里简短地记录一下我的收获吧。</p><a id="more"></a><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>第一篇概述主要从大型网站的架构演化、架构模式和核心构架要素三个方面对大型网站技术架构进行了一个综合性的概述。</p><p>大型网站架构演化发展历程主要经过以下几个阶段：</p><ul><li>初始阶段的网站架构，能够跑来就行，所有的资源放在一个服务器上</li><li>应用服务和数据服务分离，随着需求的不断扩大，将应用和数据根据对硬件资源的要求不同分离成应用服务器、文件服务器和数据库服务器</li><li>使用缓存改善网站性能，根据二八定律把经常访问的一小部分数据缓存在内存中，减少对数据库访问的压力，改善网站性能</li><li>使用应用服务器集群改善网站的并发处理能力，通过负载均衡调度服务器，将请求分发到应用服务器集群中的任何一台服务器中</li><li>数据库读写分离，对数据库读的需求远大于写的需求，应用服务器在写数据的时候，访问主数据库，主数据库通过主从复制机制将数据更新同步到从数据库</li><li>使用反向代理和CDN加速网站响应，CDN解决不同的地区其访问性能有较大差异的情况，反向代理是应用在应用服务器的一种缓存手段</li><li>使用分布式文件系统和分布式数据库系统，分布式数据库是网站数据库拆分的最后手段</li><li>使用 NOSQL和搜索引擎，提供对可伸缩性的更好支持</li><li>业务拆分</li><li>分布式服务</li></ul><p>架构模式中讲了9种解决大型网站一系列问题的解决方案：</p><ul><li>分层，将系统在横向维度上切分成几个部分，使得每一个部门负责的职能比较单一</li><li>分割，在纵向方面对软件进行切分，将不同的功能和服务分割开来,包装成高内聚低耦合的模块单元</li><li>分布式，将分层和分割后的模块独立部署，达到处理更大的并发访问</li><li>集群，多台服务器部署相同应用构成一个集群,通过负载均衡设备共同对外提供服务</li><li>缓存，将数据存放在距离计算最近的位置以加快处理速度</li><li>异步，业务之间的消息传递不是同步调用,而是将一个业务操作分成多个阶段,每个阶段之间通过共享数据的方式异步执行进行协作</li><li>冗余，为网站的高可用性提供保障</li><li>自动化，在无人值守的情况下网站可以正常运行,一切都可以自动化是网站的理想状态</li><li>安全，互联网的开放特性使得其从诞生起就面对巨大的安全挑战</li></ul><p>核心架构要素中讲了5个核心架构要素：</p><ol><li>性能，访问的响应时间、TPS、系统性能计数器决定</li><li>可用性，高可用设计的目标就是当服务器宕机的时候,服务或者应用依然可用</li><li>伸缩性，通过不断向集群中加入服务器的手段来缓解不断上升的用户并发访问压力和不断增长的数据存储需求</li><li>扩展性，使网站能够快速响应需求变化，网站可伸缩架构的主要手段是事件驱动架构和分布式服务</li><li>安全性，保护网站不受恶意访问和攻击,保护网站的重要数据不被窃取</li></ol><h2 id="导图"><a href="#导图" class="headerlink" title="导图"></a>导图</h2><p>后面的内容比较多，我就整理成思维导图的形式吧</p><p><img src="http://data3.liuin.cn/2018-06-17-15292395484469.jpg" alt=""></p><p>展开的高清大图可以看<a href="https://data2.liuin.cn/2018-060-18-%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84.svg" target="_blank" rel="noopener">这里</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近用了三天的时间把《大型网站技术架构》看完了，收获颇多。这本书主要讲了一个网站从小到大发展过程在技术架构上的需要注意的地方，这里简短地记录一下我的收获吧。&lt;/p&gt;
    
    </summary>
    
      <category term="读书笔记" scheme="https://www.liuin.cn/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
  </entry>
  
  <entry>
    <title>Redis源码剖析——事件</title>
    <link href="https://www.liuin.cn/2018/06/16/Redis%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90%E2%80%94%E2%80%94%E4%BA%8B%E4%BB%B6/"/>
    <id>https://www.liuin.cn/2018/06/16/Redis源码剖析——事件/</id>
    <published>2018-06-16T10:10:22.000Z</published>
    <updated>2019-01-06T14:12:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>对RDB处理事件的的过程实现进行分析</p><a id="more"></a><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件</p><ul><li>文件事件( file event): Redis服务器通过套接字与客户端(或者其他 Redis服务器)进行连接,而文件事件就是服务器对套接字操作的抽象。服务器与客户端(或者其他服务器)的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。</li><li>时间事件( time event): Redis服务器中的一些操作需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象</li></ul><h2 id="文件事件"><a href="#文件事件" class="headerlink" title="文件事件"></a>文件事件</h2><p>Redis基于Reactor模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器( fle event handler)</p><ul><li>文件事件处理器使用I/o多路复用( multiplexing)程序来<strong>同时监听多个套接字</strong>,并根据套接字目前执行的任务来为套接字关联不同的事件处理器</li><li>当被监听的套接字准备好执行连接应答( accept)、读取(read)、写人( wrte)关闭( close)等操作时,与操作相对应的文件事件就会产生,这时<strong>文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件</strong>。</li></ul><p>虽然文件事件处理器以单线程方式运行,但通过使用I/O多路复用程序来监听多个套接字,文件事件处理器既实现了<strong>高性能的网络通信模型</strong>,又可以很好地与 Redis服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis内部单线程设计的简单性。</p><p><img src="http://data3.liuin.cn/2018-06-17-15292019497333.jpg" alt=""></p><h3 id="事件结构定义"><a href="#事件结构定义" class="headerlink" title="事件结构定义"></a>事件结构定义</h3><p>在Redis中事件结构体的定义如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">aeFileEvent</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> mask;   <span class="comment">// 读or写标记</span></span><br><span class="line">    aeFileProc *rfileProc;  <span class="comment">// 读处理函数</span></span><br><span class="line">    aeFileProc *wfileProc;  <span class="comment">// 写处理函数</span></span><br><span class="line">    <span class="keyword">void</span> *clientData;  <span class="comment">// 私有数据</span></span><br><span class="line">&#125; aeFileEvent;</span><br></pre></td></tr></table></figure></p><h3 id="事件的创建和删除"><a href="#事件的创建和删除" class="headerlink" title="事件的创建和删除"></a>事件的创建和删除</h3><p>针对事件的创建和删除的API有：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建文件事件</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">aeCreateFileEvent</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> fd, <span class="keyword">int</span> mask,</span></span></span><br><span class="line"><span class="function"><span class="params">        aeFileProc *proc, <span class="keyword">void</span> *clientData)</span></span>;</span><br><span class="line"><span class="comment">// 删除文件事件</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">aeDeleteFileEvent</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> fd, <span class="keyword">int</span> mask)</span></span>;</span><br><span class="line"><span class="comment">// 根据文件描述符获取文件事件</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">aeGetFileEvents</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> fd)</span></span>;</span><br></pre></td></tr></table></figure></p><p>这些接口的实现都比较简单，就是在<code>eventLoop</code>这个事件池中创建（删除）指定属性的事件</p><p>需要使用到事件的创建的地方有两个：</p><ul><li>一个是在初始化服务器的时候，需要添加一个对应套接字描述符的监听套接字来监听新的客户端连接</li><li>新的客户端连接的时候，需要添加一个文件事件来监听这个客户端的请求</li></ul><h3 id="I-O多路复用"><a href="#I-O多路复用" class="headerlink" title="I/O多路复用"></a>I/O多路复用</h3><p>在Linux/Unix中实现I/O多路复用的方法有非常多，大致有select、 epoll、 export和 kqueue这些IO多路复用函数库来实现的</p><p>各种实现的性能也是不一样的，之前我写了一篇博客<a href="/2018/04/20/I-O%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E4%B9%8BSelect%E3%80%81Poll%E5%92%8CEpoll/">对比了三种I/O多路复用</a></p><p>在Redis中，其会根据具体底层操作系统的不同自动选择系统中性能最高的I/O多路复用函数库来作为 Redis的I/O多路复用程序的底层实现（从程序中看，其性能的排行应该是evport &gt; epoll &gt; kqueue &gt; select ）:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Include the best multiplexing layer supported by this system.</span></span><br><span class="line"><span class="comment"> * The following should be ordered by performances, descending. */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> HAVE_EVPORT</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ae_evport.c"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">ifdef</span> HAVE_EPOLL</span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ae_epoll.c"</span></span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line">        <span class="meta">#<span class="meta-keyword">ifdef</span> HAVE_KQUEUE</span></span><br><span class="line">        <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ae_kqueue.c"</span></span></span><br><span class="line">        <span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line">        <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ae_select.c"</span></span></span><br><span class="line">        <span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>各种不同的I/O多路复用库的使用方式是不一样的，所以Redis对功能进行了统一的封装，方便在不同的环境下的使用：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建，初始化</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">aeApiCreate</span><span class="params">(aeEventLoop *eventLoop)</span></span>;</span><br><span class="line"><span class="comment">// 改变能够监听事件的大小值</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">aeApiResize</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> setsize)</span></span>;</span><br><span class="line"><span class="comment">// 清空</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">aeApiFree</span><span class="params">(aeEventLoop *eventLoop)</span></span>;</span><br><span class="line"><span class="comment">// 添加监听事件</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">aeApiAddEvent</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> fd, <span class="keyword">int</span> mask)</span></span>;</span><br><span class="line"><span class="comment">// 删除监听事件</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">aeApiDelEvent</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> fd, <span class="keyword">int</span> delmask)</span></span>;</span><br><span class="line"><span class="comment">// 取出已经就绪的文件描述符</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">aeApiPoll</span><span class="params">(aeEventLoop *eventLoop, struct timeval *tvp)</span></span></span><br></pre></td></tr></table></figure><p>下面以我比较熟悉的epoll为例查看封装的实现：</p><p>首先定义一个ae状态结构体，事实上就是epoll的文件描述符和一个获取监听事件中就绪文件描述符的文件表<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">aeApiState</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> epfd;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">epoll_event</span> *<span class="title">events</span>;</span></span><br><span class="line">&#125; aeApiState;</span><br></pre></td></tr></table></figure></p><p>创建的过程：<br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">aeApiCreate</span><span class="params">(aeEventLoop *eventLoop)</span> </span>&#123;</span><br><span class="line">    aeApiState *state = zmalloc(<span class="keyword">sizeof</span>(aeApiState));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!state) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="comment">// 监听指定大小的事件数量</span></span><br><span class="line">    state-&gt;events = zmalloc(<span class="keyword">sizeof</span>(struct epoll_event)*eventLoop-&gt;setsize);</span><br><span class="line">    <span class="keyword">if</span> (!state-&gt;events) &#123;</span><br><span class="line">        zfree(state);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 创建epoll</span></span><br><span class="line">    state-&gt;epfd = epoll_create(<span class="number">1024</span>); <span class="comment">/* 1024 is just a hint for the kernel */</span></span><br><span class="line">    <span class="keyword">if</span> (state-&gt;epfd == <span class="number">-1</span>) &#123;</span><br><span class="line">        zfree(state-&gt;events);</span><br><span class="line">        zfree(state);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 指定数据</span></span><br><span class="line">    eventLoop-&gt;apidata = state;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>添加监听事件过程：<br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">aeApiAddEvent</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> fd, <span class="keyword">int</span> mask)</span> </span>&#123;</span><br><span class="line">    aeApiState *state = eventLoop-&gt;apidata;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">epoll_event</span> <span class="title">ee</span>;</span></span><br><span class="line">    <span class="comment">/* If the fd was already monitored for some event, we need a MOD</span></span><br><span class="line"><span class="comment">     * operation. Otherwise we need an ADD operation. */</span></span><br><span class="line">    <span class="keyword">int</span> op = eventLoop-&gt;events[fd].mask == AE_NONE ?</span><br><span class="line">            EPOLL_CTL_ADD : EPOLL_CTL_MOD;</span><br><span class="line"></span><br><span class="line">    ee.events = <span class="number">0</span>;</span><br><span class="line">    mask |= eventLoop-&gt;events[fd].mask; <span class="comment">/* Merge old events */</span></span><br><span class="line">    <span class="comment">// 根据时间的mask来决定监听读or写就绪</span></span><br><span class="line">    <span class="keyword">if</span> (mask &amp; AE_READABLE) ee.events |= EPOLLIN;</span><br><span class="line">    <span class="keyword">if</span> (mask &amp; AE_WRITABLE) ee.events |= EPOLLOUT;</span><br><span class="line">    ee.data.u64 = <span class="number">0</span>; <span class="comment">/* avoid valgrind warning */</span></span><br><span class="line">    ee.data.fd = fd;</span><br><span class="line">    <span class="comment">// 添加监听事件到内核中</span></span><br><span class="line">    <span class="keyword">if</span> (epoll_ctl(state-&gt;epfd,op,fd,&amp;ee) == <span class="number">-1</span>) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h3 id="文件事件的处理"><a href="#文件事件的处理" class="headerlink" title="文件事件的处理"></a>文件事件的处理</h3><p>I/O多路复用接收到了就绪的事件的时候，就需要对事件进行处理，通过文件事件分派器来分派给不同的文件事件处理器，具体需要处理的文件事件类型如下：</p><ul><li>为了对连接服务器的各个客户端进行应答,服务器要为监听套接字关联连接应答处理器。</li><li>为了接收客户端传来的命令请求,服务器要为客户端套接字关联命令请求处理器。</li><li>为了向客户端返回命令的执行结果,服务器要为客户端套接字关联命令回复处理器。</li><li>当主服务器和从服务器进行复制操作时,主从服务器都需要关联特别为复制功能编写的复制处理器。</li></ul><p>值得注意的是连接应答处理时，需要新添加一个监听事件</p><blockquote><p>连接应答处理</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">acceptTcpHandler</span><span class="params">(aeEventLoop *el, <span class="keyword">int</span> fd, <span class="keyword">void</span> *privdata, <span class="keyword">int</span> mask)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> cport, cfd, max = MAX_ACCEPTS_PER_CALL;</span><br><span class="line">    <span class="keyword">char</span> cip[REDIS_IP_STR_LEN];</span><br><span class="line">    REDIS_NOTUSED(el);</span><br><span class="line">    REDIS_NOTUSED(mask);</span><br><span class="line">    REDIS_NOTUSED(privdata);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span>(max--) &#123;</span><br><span class="line">        <span class="comment">// accept 客户端连接</span></span><br><span class="line">        cfd = anetTcpAccept(server.neterr, fd, cip, <span class="keyword">sizeof</span>(cip), &amp;cport);</span><br><span class="line">        <span class="keyword">if</span> (cfd == ANET_ERR) &#123;</span><br><span class="line">            <span class="keyword">if</span> (errno != EWOULDBLOCK)</span><br><span class="line">                redisLog(REDIS_WARNING,</span><br><span class="line">                    <span class="string">"Accepting client connection: %s"</span>, server.neterr);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        redisLog(REDIS_VERBOSE,<span class="string">"Accepted %s:%d"</span>, cip, cport);</span><br><span class="line">        <span class="comment">// 为客户端创建客户端状态（redisClient）</span></span><br><span class="line">        acceptCommonHandler(cfd,<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过Redis对上面几种事件的应答处理，我们可以得出客户端和服务端的通信模型如下：</p><p><img src="http://data3.liuin.cn/2018-06-17-15292047392028.jpg" alt=""></p><h2 id="时间事件"><a href="#时间事件" class="headerlink" title="时间事件"></a>时间事件</h2><p>Redis的时间事件分为以下两类:</p><ul><li>定时事件:让一段程序在指定的时间之后执行一次。比如说,让程序X在当前时间的30毫秒之后执行一次。</li><li>周期性事件:让一段程序每隔指定时间就执行一次。比如说,让程序Y每隔30毫秒就执行一次。</li></ul><h3 id="事件结构定义-1"><a href="#事件结构定义-1" class="headerlink" title="事件结构定义"></a>事件结构定义</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">aeTimeEvent</span> &#123;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 时间事件的唯一标识符</span></span><br><span class="line">    <span class="keyword">long</span> <span class="keyword">long</span> id; <span class="comment">/* time event identifier. */</span></span><br><span class="line">    <span class="comment">// 事件的到达时间</span></span><br><span class="line">    <span class="keyword">long</span> when_sec; <span class="comment">/* seconds */</span></span><br><span class="line">    <span class="keyword">long</span> when_ms; <span class="comment">/* milliseconds */</span></span><br><span class="line">    <span class="comment">// 事件处理函数</span></span><br><span class="line">    aeTimeProc *timeProc;</span><br><span class="line">    <span class="comment">// 事件释放函数</span></span><br><span class="line">    aeEventFinalizerProc *finalizerProc;</span><br><span class="line">    <span class="comment">// 多路复用库的私有数据</span></span><br><span class="line">    <span class="keyword">void</span> *clientData;</span><br><span class="line">    <span class="comment">// 指向下个时间事件结构，形成链表</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">aeTimeEvent</span> *<span class="title">next</span>;</span></span><br><span class="line"></span><br><span class="line">&#125; aeTimeEvent;</span><br></pre></td></tr></table></figure><p>服务器将所有时间事件都放在一个无序链表中,每当时间事件执行器运行时,它就遍历整个链表,查找所有已到达的时间事件,并调用相应的事件处理器。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 已就绪事件</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">aeFiredEvent</span> &#123;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 已就绪文件描述符</span></span><br><span class="line">    <span class="keyword">int</span> fd;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 事件类型掩码，</span></span><br><span class="line">    <span class="comment">// 值可以是 AE_READABLE 或 AE_WRITABLE</span></span><br><span class="line">    <span class="comment">// 或者是两者的或</span></span><br><span class="line">    <span class="keyword">int</span> mask;</span><br><span class="line"></span><br><span class="line">&#125; aeFiredEvent;</span><br></pre></td></tr></table></figure><h3 id="时间事件相关API"><a href="#时间事件相关API" class="headerlink" title="时间事件相关API"></a>时间事件相关API</h3><p>时间事件相关API如下：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建时间事件</span></span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="keyword">long</span> <span class="title">aeCreateTimeEvent</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">long</span> <span class="keyword">long</span> milliseconds,</span></span></span><br><span class="line"><span class="function"><span class="params">        aeTimeProc *proc, <span class="keyword">void</span> *clientData,</span></span></span><br><span class="line"><span class="function"><span class="params">        aeEventFinalizerProc *finalizerProc)</span></span>;</span><br><span class="line"><span class="comment">// 删除时间事件</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">aeDeleteTimeEvent</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">long</span> <span class="keyword">long</span> id)</span></span>;</span><br><span class="line"><span class="comment">// 时间事件的执行器</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">aeProcessEvents</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> flags)</span></span>;</span><br><span class="line"><span class="comment">// 返回最近的时间事件</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> aeTimeEvent *<span class="title">aeSearchNearestTimer</span><span class="params">(aeEventLoop *eventLoop)</span></span>;</span><br></pre></td></tr></table></figure><p>创建和删除时间事件的实现都比较简单，相当于构造和析构函数，我们先看看时间事件执行器的实现：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">processTimeEvents</span><span class="params">(aeEventLoop *eventLoop)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> processed = <span class="number">0</span>;</span><br><span class="line">    aeTimeEvent *te;</span><br><span class="line">    <span class="keyword">long</span> <span class="keyword">long</span> maxId;</span><br><span class="line">    <span class="keyword">time_t</span> now = time(<span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过重置事件的运行时间，</span></span><br><span class="line">    <span class="comment">// 防止因时间穿插（skew）而造成的事件处理混乱</span></span><br><span class="line">    <span class="keyword">if</span> (now &lt; eventLoop-&gt;lastTime) &#123;</span><br><span class="line">        te = eventLoop-&gt;timeEventHead;</span><br><span class="line">        <span class="keyword">while</span>(te) &#123;</span><br><span class="line">            te-&gt;when_sec = <span class="number">0</span>;</span><br><span class="line">            te = te-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 更新最后一次处理时间事件的时间</span></span><br><span class="line">    eventLoop-&gt;lastTime = now;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 遍历链表</span></span><br><span class="line">    <span class="comment">// 执行那些已经到达的事件</span></span><br><span class="line">    te = eventLoop-&gt;timeEventHead;</span><br><span class="line">    maxId = eventLoop-&gt;timeEventNextId<span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">while</span>(te) &#123;</span><br><span class="line">        <span class="keyword">long</span> now_sec, now_ms;</span><br><span class="line">        <span class="keyword">long</span> <span class="keyword">long</span> id;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 跳过无效事件</span></span><br><span class="line">        <span class="keyword">if</span> (te-&gt;id &gt; maxId) &#123;</span><br><span class="line">            te = te-&gt;next;</span><br><span class="line">            <span class="keyword">continue</span>;</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">        aeGetTime(&amp;now_sec, &amp;now_ms);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果当前时间等于或等于事件的执行时间，那么说明事件已到达，执行这个事件</span></span><br><span class="line">        <span class="keyword">if</span> (now_sec &gt; te-&gt;when_sec ||</span><br><span class="line">            (now_sec == te-&gt;when_sec &amp;&amp; now_ms &gt;= te-&gt;when_ms))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> retval;</span><br><span class="line"></span><br><span class="line">            id = te-&gt;id;</span><br><span class="line">            <span class="comment">// 执行事件处理器，并获取返回值</span></span><br><span class="line">            retval = te-&gt;timeProc(eventLoop, id, te-&gt;clientData);</span><br><span class="line">            processed++;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 记录是否有需要循环执行这个事件时间</span></span><br><span class="line">            <span class="keyword">if</span> (retval != AE_NOMORE) &#123;</span><br><span class="line">                <span class="comment">// 是的， retval 毫秒之后继续执行这个时间事件</span></span><br><span class="line">                aeAddMillisecondsToNow(retval,&amp;te-&gt;when_sec,&amp;te-&gt;when_ms);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 不，将这个事件删除</span></span><br><span class="line">                aeDeleteTimeEvent(eventLoop, id);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 因为执行事件之后，事件列表可能已经被改变了</span></span><br><span class="line">            <span class="comment">// 因此需要将 te 放回表头，继续开始执行事件</span></span><br><span class="line">            te = eventLoop-&gt;timeEventHead;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            te = te-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> processed;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其总体的思想是：遍历所有已到达的时间事件并调用这些事件的处理器。已到达指的是,时间事件的when属性记录的UNIX时间截等于或小于当前时间的UNIX时间戳。</p><p><code>aeSearchNearestTimer</code>返回目前时间最近的时间事件</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 寻找里目前时间最近的时间事件</span></span><br><span class="line"><span class="comment">// 因为链表是乱序的，所以查找复杂度为 O（N）</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> aeTimeEvent *<span class="title">aeSearchNearestTimer</span><span class="params">(aeEventLoop *eventLoop)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    aeTimeEvent *te = eventLoop-&gt;timeEventHead;</span><br><span class="line">    aeTimeEvent *nearest = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span>(te) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!nearest || te-&gt;when_sec &lt; nearest-&gt;when_sec ||</span><br><span class="line">                (te-&gt;when_sec == nearest-&gt;when_sec &amp;&amp;</span><br><span class="line">                 te-&gt;when_ms &lt; nearest-&gt;when_ms))</span><br><span class="line">            nearest = te;</span><br><span class="line">        te = te-&gt;next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> nearest;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="时间事件的处理"><a href="#时间事件的处理" class="headerlink" title="时间事件的处理"></a>时间事件的处理</h3><p>时间事件的主要处理应用在<code>serverCron</code>中，其函数的主要工作有：</p><ul><li>更新服务器的各类统计信息，比如时间、内存占用、数据库占用等</li><li>清理数据库中的过期键值对</li><li>关闭和清理连接失效的客户端</li><li>尝试进行AOF和RDB持久化操作</li><li>如果是主服务器，就对从服务器进行定期同步</li><li>如果是集群模式，对集群进行定期同步和连接测试</li></ul><h2 id="事件循环"><a href="#事件循环" class="headerlink" title="事件循环"></a>事件循环</h2><p>时间事件和文件事件都在一个事件循环结构体中</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">aeEventLoop</span> &#123;</span></span><br><span class="line">    <span class="comment">// 目前已注册的最大描述符</span></span><br><span class="line">    <span class="keyword">int</span> maxfd;   <span class="comment">/* highest file descriptor currently registered */</span></span><br><span class="line">    <span class="comment">// 目前已追踪的最大描述符</span></span><br><span class="line">    <span class="keyword">int</span> setsize; <span class="comment">/* max number of file descriptors tracked */</span></span><br><span class="line">    <span class="comment">// 用于生成时间事件 id</span></span><br><span class="line">    <span class="keyword">long</span> <span class="keyword">long</span> timeEventNextId;</span><br><span class="line">    <span class="comment">// 最后一次执行时间事件的时间</span></span><br><span class="line">    <span class="keyword">time_t</span> lastTime;     <span class="comment">/* Used to detect system clock skew */</span></span><br><span class="line">    <span class="comment">// 已注册的文件事件</span></span><br><span class="line">    aeFileEvent *events; <span class="comment">/* Registered events */</span></span><br><span class="line">    <span class="comment">// 已就绪的文件事件</span></span><br><span class="line">    aeFiredEvent *fired; <span class="comment">/* Fired events */</span></span><br><span class="line">    <span class="comment">// 时间事件</span></span><br><span class="line">    aeTimeEvent *timeEventHead;</span><br><span class="line">    <span class="comment">// 事件处理器的开关</span></span><br><span class="line">    <span class="keyword">int</span> stop;</span><br><span class="line">    <span class="comment">// 多路复用库的私有数据</span></span><br><span class="line">    <span class="keyword">void</span> *apidata; <span class="comment">/* This is used for polling API specific data */</span></span><br><span class="line">    <span class="comment">// 在处理事件前要执行的函数</span></span><br><span class="line">    aeBeforeSleepProc *beforesleep;</span><br><span class="line"></span><br><span class="line">&#125; aeEventLoop;</span><br></pre></td></tr></table></figure><p>在加入事件到进行处理事件中间的环节就是事件循环了，其调用的是<code>aeMain</code>函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">aeMain</span><span class="params">(aeEventLoop *eventLoop)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    eventLoop-&gt;stop = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (!eventLoop-&gt;stop) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果有需要在事件处理前执行的函数，那么运行它</span></span><br><span class="line">        <span class="keyword">if</span> (eventLoop-&gt;beforesleep != <span class="literal">NULL</span>)</span><br><span class="line">            eventLoop-&gt;beforesleep(eventLoop);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 开始处理事件</span></span><br><span class="line">        aeProcessEvents(eventLoop, AE_ALL_EVENTS);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，当服务器开始运行的时候，事件循环就不停运行，其事件处理函数<code>aeProcessEvents</code>实现如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">aeProcessEvents</span><span class="params">(aeEventLoop *eventLoop, <span class="keyword">int</span> flags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> processed = <span class="number">0</span>, numevents;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Nothing to do? return ASAP */</span></span><br><span class="line">    <span class="keyword">if</span> (!(flags &amp; AE_TIME_EVENTS) &amp;&amp; !(flags &amp; AE_FILE_EVENTS)) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (eventLoop-&gt;maxfd != <span class="number">-1</span> ||</span><br><span class="line">        ((flags &amp; AE_TIME_EVENTS) &amp;&amp; !(flags &amp; AE_DONT_WAIT))) &#123;</span><br><span class="line">        <span class="keyword">int</span> j;</span><br><span class="line">        aeTimeEvent *shortest = <span class="literal">NULL</span>;</span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">tv</span>, *<span class="title">tvp</span>;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取最近的时间事件</span></span><br><span class="line">        <span class="keyword">if</span> (flags &amp; AE_TIME_EVENTS &amp;&amp; !(flags &amp; AE_DONT_WAIT))</span><br><span class="line">            shortest = aeSearchNearestTimer(eventLoop);</span><br><span class="line">        <span class="keyword">if</span> (shortest) &#123;</span><br><span class="line">            <span class="comment">// 如果时间事件存在的话</span></span><br><span class="line">            <span class="comment">// 那么根据最近可执行时间事件和现在时间的时间差来决定文件事件的阻塞时间</span></span><br><span class="line">            <span class="keyword">long</span> now_sec, now_ms;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 计算距今最近的时间事件还要多久才能达到</span></span><br><span class="line">            <span class="comment">// 并将该时间距保存在 tv 结构中</span></span><br><span class="line">            aeGetTime(&amp;now_sec, &amp;now_ms);</span><br><span class="line">            tvp = &amp;tv;</span><br><span class="line">            tvp-&gt;tv_sec = shortest-&gt;when_sec - now_sec;</span><br><span class="line">            <span class="keyword">if</span> (shortest-&gt;when_ms &lt; now_ms) &#123;</span><br><span class="line">                tvp-&gt;tv_usec = ((shortest-&gt;when_ms+<span class="number">1000</span>) - now_ms)*<span class="number">1000</span>;</span><br><span class="line">                tvp-&gt;tv_sec --;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                tvp-&gt;tv_usec = (shortest-&gt;when_ms - now_ms)*<span class="number">1000</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 时间差小于 0 ，说明事件已经可以执行了，将秒和毫秒设为 0 （不阻塞）</span></span><br><span class="line">            <span class="keyword">if</span> (tvp-&gt;tv_sec &lt; <span class="number">0</span>) tvp-&gt;tv_sec = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">if</span> (tvp-&gt;tv_usec &lt; <span class="number">0</span>) tvp-&gt;tv_usec = <span class="number">0</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 执行到这一步，说明没有时间事件</span></span><br><span class="line">            <span class="comment">// 那么根据 AE_DONT_WAIT 是否设置来决定是否阻塞，以及阻塞的时间长度</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (flags &amp; AE_DONT_WAIT) &#123;</span><br><span class="line">                <span class="comment">// 设置文件事件不阻塞</span></span><br><span class="line">                tv.tv_sec = tv.tv_usec = <span class="number">0</span>;</span><br><span class="line">                tvp = &amp;tv;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">/* Otherwise we can block */</span></span><br><span class="line">                <span class="comment">// 文件事件可以阻塞直到有事件到达为止</span></span><br><span class="line">                tvp = <span class="literal">NULL</span>; <span class="comment">/* wait forever */</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 处理文件事件，阻塞时间由 tvp 决定</span></span><br><span class="line">        numevents = aeApiPoll(eventLoop, tvp);</span><br><span class="line">        <span class="keyword">for</span> (j = <span class="number">0</span>; j &lt; numevents; j++) &#123;</span><br><span class="line">            <span class="comment">// 从已就绪数组中获取事件</span></span><br><span class="line">            aeFileEvent *fe = &amp;eventLoop-&gt;events[eventLoop-&gt;fired[j].fd];</span><br><span class="line"></span><br><span class="line">            <span class="keyword">int</span> mask = eventLoop-&gt;fired[j].mask;</span><br><span class="line">            <span class="keyword">int</span> fd = eventLoop-&gt;fired[j].fd;</span><br><span class="line">            <span class="keyword">int</span> rfired = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 读事件</span></span><br><span class="line">            <span class="keyword">if</span> (fe-&gt;mask &amp; mask &amp; AE_READABLE) &#123;</span><br><span class="line">                <span class="comment">// rfired 确保读/写事件只能执行其中一个</span></span><br><span class="line">                rfired = <span class="number">1</span>;</span><br><span class="line">                fe-&gt;rfileProc(eventLoop,fd,fe-&gt;clientData,mask);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 写事件</span></span><br><span class="line">            <span class="keyword">if</span> (fe-&gt;mask &amp; mask &amp; AE_WRITABLE) &#123;</span><br><span class="line">                <span class="keyword">if</span> (!rfired || fe-&gt;wfileProc != fe-&gt;rfileProc)</span><br><span class="line">                    fe-&gt;wfileProc(eventLoop,fd,fe-&gt;clientData,mask);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            processed++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Check time events */</span></span><br><span class="line">    <span class="comment">// 执行时间事件</span></span><br><span class="line">    <span class="keyword">if</span> (flags &amp; AE_TIME_EVENTS)</span><br><span class="line">        processed += processTimeEvents(eventLoop);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> processed; <span class="comment">/* return the number of processed file/time events */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其主题的逻辑如下：</p><ul><li>查找最早的时间事件，判断是否需要执行，如需要，就标记下来，等待处理，并确定后面处理文件事件的阻塞时间</li><li>获取已准备好的文件事件描述符集</li><li>优先处理读事件</li><li>处理写事件</li><li>如有时间事件，就处理时间事件</li></ul><p><img src="http://data3.liuin.cn/2018-06-17-15292178140041.jpg" alt=""></p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>通过对Redis的时间事件和文件事件的解析，能够了解Redis客户端和服务端交互的基本过程，同时也能够了解到Redis是单线程的，整个事件循环是串行的</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;对RDB处理事件的的过程实现进行分析&lt;/p&gt;
    
    </summary>
    
      <category term="源码剖析" scheme="https://www.liuin.cn/categories/%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
      <category term="Redis" scheme="https://www.liuin.cn/tags/Redis/"/>
    
  </entry>
  
</feed>
