<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>牟宗琳的博客</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://yoursite.com/"/>
  <updated>2020-01-17T05:29:07.862Z</updated>
  <id>http://yoursite.com/</id>
  
  <author>
    <name>John Doe</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>InnoDB MVCC</title>
    <link href="http://yoursite.com/2020/01/17/InnoDB%20MVCC/"/>
    <id>http://yoursite.com/2020/01/17/InnoDB MVCC/</id>
    <published>2020-01-17T02:59:26.126Z</published>
    <updated>2020-01-17T05:29:07.862Z</updated>
    
    <content type="html"><![CDATA[<p>参考文章：</p><p><a href="https://mp.weixin.qq.com/s/bM_g6Z0K93DNFycvfJIbwQ" target="_blank" rel="noopener">数据库村的旺财和小强</a></p><p><a href="https://mp.weixin.qq.com/s/BUvlwrupp0v89qblpwAoyA" target="_blank" rel="noopener">再谈 InnoDB MVCC 机制</a></p><h3 id="InnoDB-MVCC"><a href="#InnoDB-MVCC" class="headerlink" title="InnoDB MVCC"></a>InnoDB MVCC</h3><h4 id="1-什么是MVCC"><a href="#1-什么是MVCC" class="headerlink" title="1.什么是MVCC"></a>1.什么是MVCC</h4><p>MVCC（Multiversion Concurrency Control），中文全称叫中文全称叫<strong>多版本并发控制</strong>，是现代数据库（包括 MySQL、Oracle、PostgreSQL 等）引擎实现中常用的处理读写冲突的手段，<strong>目的在于提高数据库高并发场景下的吞吐性能</strong>。</p><p>如此一来不同的事务在并发过程中，SELECT 操作可以不加锁而是通过 MVCC 机制读取指定的版本历史记录，并通过一些手段保证保证读取的记录值符合事务所处的隔离级别，从而解决并发场景下的读写冲突。</p><p>下面举一个多版本读的例子，例如两个事务 A 和 B 按照如下顺序进行更新和读取操作</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture1.jpeg" alt="picture"></p><p>在事务 A 提交前后，事务 B 读取到的 x 的值是什么呢？答案是：事务 B 在不同的隔离级别下，读取到的值不一样。</p><ol><li>如果事务 B 的隔离级别是读未提交（RU），那么两次读取均读取到 x 的最新值，即 20。</li><li>如果事务 B 的隔离级别是读已提交（RC），那么第一次读取到旧值 10，第二次因为事务 A 已经提交，则读取到新值 20。</li><li>如果事务 B 的隔离级别是可重复读或者串行（RR，S），则两次均读到旧值 10，不论事务 A 是否已经提交。</li></ol><p>可见在不同的隔离级别下，数据库通过 MVCC 和隔离级别，让事务之间并行操作遵循了某种规则，来保证单个事务内前后数据的一致性。</p><h4 id="2-为什么需要MVCC"><a href="#2-为什么需要MVCC" class="headerlink" title="2.为什么需要MVCC"></a>2.为什么需要MVCC</h4><p>InnoDB 相比 MyISAM 有两大特点，一是支持事务而是支持行级锁，事务的引入带来了一些新的挑战。相对于串行处理来说，并发事务处理能大大增加数据库资源的利用率，提高数据库系统的事务吞吐量，从而可以支持可以支持更多的用户。但并发事务处理也会带来一些问题，主要包括以下几种情况：</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture9.jpg" alt>      </p><p><strong>实现隔离机制的方法主要有两种</strong>：</p><ol><li>加读写锁</li><li>一致性快照读，即 MVCC</li></ol><h4 id="3-InnoDB-MVCC实现原理"><a href="#3-InnoDB-MVCC实现原理" class="headerlink" title="3.InnoDB MVCC实现原理"></a>3.InnoDB MVCC实现原理</h4><p>InnoDB中MVCC的实现方式为：每一行记录都有两个隐藏列：DATA_TRX_ID,DATA_ROLL_PTR(如果没有主键，则还会多一个隐藏的主键列)。</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture2.jpeg" alt="picture"></p><p><strong>DATA_TRX_ID</strong></p><p>记录最近更新这条行记录的事务ID，大小为6个字节</p><p><strong>DATA_ROLL_PTR</strong></p><p>表示指向该回滚段(rollback segment)的 指针，大小为7个字节，InnoDB便是通过这个指针找到之前版本的数据，改行记录上所有旧版本，在undo中投通过链表形式组织。</p><p><strong>DB_ROW_ID</strong></p><p>行标识（隐藏单调自增id）,大小为6字节，如果没有主键,InnoDB会自动生成一个隐藏主键，因此会出现这个列</p><p>。另外，每条记录的头信息(record header)里都有一个专门的bit（deleted_flag）来表示当前记录是否已经删除。</p><h5 id="3-1-如何组织版本链"><a href="#3-1-如何组织版本链" class="headerlink" title="3.1 如何组织版本链"></a>3.1 如何组织版本链</h5><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">关于Redo log 和 Undo log的相关概念自行查找资料</span><br></pre></td></tr></table></figure><p>上文提到，在多个事务并行操作某行数据的情况下，不同事务对该行数据的 UPDATE 会产生多个版本，然后通过回滚指针组织成一条 Undo Log 链，这节我们通过一个简单的例子来看一下 Undo Log 链是如何组织的，DATA_TRX_ID 和 DATA_ROLL_PTR 两个参数在其中又起到什么样的作用。</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture1.jpeg" alt="picture1"></p><p>还是以上文 MVCC 的例子，事务 A 对值 x 进行更新之后，该行即产生一个新版本和旧版本。假设之前插入该行的事务 ID 为 100，事务 A 的 ID 为 200，该行的隐藏主键为 1。</p><p>事务A 的操作过程为:</p><ol><li>对 DB_ROW_ID = 1 的这行记录加排他锁。</li><li>把该行原本的值拷贝到 undo log 中，DB_TRX_ID 和 DB_ROLL_PTR 都不动。</li><li><strong>修改该行的值这时产生一个新版本，更新 DATA_TRX_ID 为修改记录的事务 ID，将 DATA_ROLL_PTR 指向刚刚拷贝到 undo log 链中的旧版本记录，这样就能通过 DB_ROLL_PTR 找到这条记录的历史版本。如果对同一行记录执行连续的 UPDATE，Undo Log 会组成一个链表，遍历这个链表可以看到这条记录的变迁</strong>。</li><li>记录redo log,包括undo log中的修改。</li></ol><p>那么 INSERT 和 DELETE 会怎么做呢？其实相比 UPDATE 这二者很简单，INSERT 会产生一条新纪录，它的 DATA_TRX_ID 为当前插入记录的事务 ID；DELETE 某条记录时可看成是一种特殊的 UPDATE，其实是软删，真正执行删除操作会在 commit 时，DATA_TRX_ID 则记录下删除该记录的事务 ID。</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture7.jpg" alt="picture7"></p><h5 id="3-2-如何实现一致性读-ReadView"><a href="#3-2-如何实现一致性读-ReadView" class="headerlink" title="3.2 如何实现一致性读 ReadView"></a>3.2 如何实现一致性读 ReadView</h5><p>在 RU 隔离级别下，直接读取版本的最新记录就 OK，对于 SERIALIZABLE 隔离级别，则是通过加锁互斥来访问数据，因此不需要 MVCC 的帮助。因此 MVCC 运行在 RC 和 RR这两个隔离级别下，当 InnoDB 隔离级别设置为二者其一时，在 SELECT 数据时就会用到版本链</p><blockquote><p><em>核心问题是版本链中哪些版本对当前事务可见？</em></p></blockquote><p>InnoDB 为了解决这个问题，设计了 ReadView（可读视图）的概念。</p><h5 id="3-3-RR下ReadView的生成"><a href="#3-3-RR下ReadView的生成" class="headerlink" title="3.3 RR下ReadView的生成"></a>3.3 RR下ReadView的生成</h5><p>在RR隔离级别下，每个事务touch first read时（<strong>本质上就是执行第一个 SELECT语句时，后续所有的 SELECT 都是复用这个 ReadView</strong>，其它 update, delete, insert 语句和一致性读 snapshot 的建立没有关系），会将当前系统中的所有的活跃事务拷贝到一个列表生成ReadView。</p><h4 id="4-举个例子"><a href="#4-举个例子" class="headerlink" title="4.举个例子"></a>4.举个例子</h4><p>数据表user</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture3.jpg" alt="picture3"></p><p>加入隐藏列</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture4.jpg" alt="picture4"></p><p>假如有两个事务开启</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture5.jpg" alt="picture4"></p><p>在1处时，数据是这样的：</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture4.jpg" alt="picture4"></p><p>于此同时，需要建立一个<strong>Read View的数据结构</strong>，它有三个部分：</p><ol><li>当前活跃的事务列表[101,102]</li><li>Tmin,就是活跃事务的最小值， 在这里 Tmin = 101</li><li>Tmax, 是系统中最大事务ID（不管事务是否提交）加上1。 在这里例子中，Tmax = 103</li></ol><p>在2的部分事务102做了修改，此时数据是这样的：</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture6.png" alt="picture6"></p><p><strong>关键部分，下面的算法用来判断这些数据版本哪些是是对当前事务可见的</strong></p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture7.jpeg" alt="picture6"></p><p><strong>Read View结构</strong></p><ol><li>当前活跃的事务列表[101,102]</li><li>Tmin,就是活跃事务的最小值， 在这里 Tmin = 101</li><li>Tmax, 是系统中最大事务ID（不管事务是否提交）加上1。 在这里例子中，Tmax = 103</li></ol><p>对于上述例子，第一次读取时：</p><ol><li>取出要读取的数据，取出事务ID赋值给变量tid,tid=100。</li><li>Tmin=101,tid&lt;Tmin</li><li>可见</li></ol><p>第二次读取时：</p><ol><li>tid=102</li><li>Tmin=101,当前事务事务id=101,进入下个判断流程</li><li>tid=102,Tmax=103,走入tid在ReadView中？</li><li>tid=102在ReadView中，沿着回滚指针找到上一行，将事务id赋值给tid,tid=100</li><li>读取的数据和第一次相同</li></ol><p>保证了可重复读</p><h4 id="5-一个争议点"><a href="#5-一个争议点" class="headerlink" title="5.一个争议点"></a>5.一个争议点</h4><p>并非所有的情况都能套用MVCC读的判断流程，例如RR级别下</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/share/picture10.jpg" alt></p><p>1.假设transaction A trx_id =200 ，transaction B trx_id =300,且事务B先于事务A提交，按照MVCC的判断流程，事务A生成的ReadView为[200],最新版本行记录DATA_TRX_ID=300,300比200大，照理不能访问，但是事务A实际上读到了事务B已经提交的修改。</p><h4 id="6写在最后"><a href="#6写在最后" class="headerlink" title="6写在最后"></a>6写在最后</h4><p>RC、RR 两种隔离级别的事务在执行普通的读操作时，通过访问版本链的方法，使得事务间的读写操作得以并发执行，从而提升系统性能。RC、RR 这两个隔离级别的一个很大不同就是生成 ReadView 的时间点不同，RC 在每一次 SELECT 语句前都会生成一个 ReadView，事务期间会更新，因此在其他事务提交前后所得到的 m_ids 列表可能发生变化，使得先前不可见的版本后续又突然可见了。而 RR 只在事务的第一个 SELECT 语句时生成一个 ReadView，事务操作期间不更新。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;参考文章：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/bM_g6Z0K93DNFycvfJIbwQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;数据库村的旺财和小强&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;ht
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>如何备份博客</title>
    <link href="http://yoursite.com/2020/01/17/%E5%A6%82%E4%BD%95%E5%A4%87%E4%BB%BD%E5%8D%9A%E5%AE%A2/"/>
    <id>http://yoursite.com/2020/01/17/如何备份博客/</id>
    <published>2020-01-17T02:58:44.018Z</published>
    <updated>2020-01-17T02:58:44.019Z</updated>
    
    <content type="html"><![CDATA[<p>本文转载自<a href="https://www.jianshu.com/p/baab04284923" target="_blank" rel="noopener">怎么去备份你的Hexo博客</a></p><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>作为一个写代码的人来说，保存和备份是非常重要的，所以随手保存和存有备份已经成为我的习惯了。使用Hexo在github搭建的博客，仓库里只有生成的静态网页文件，是没有Hexo的源文件的，如果现在这个电脑出现了什么问题，那就麻烦了，所以我就研究了一下怎么备份。备份的教程和搭建的教程比起来简直是少的可怜，好不容易找到一份教程，却是写的非常高深，个人觉得不应该是那么复杂的，所以果断放弃，就在我准备自己瞎搞得时候，终于让我看到了一份简单明了的教程，因为测试成功了，所以我需要把过程记录下来，以后忘了还可以有个参考</p><hr><p><em>华丽的分隔线，前面的碎碎念可以略过，下面才是正文</em>。</p><h3 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h3><p>针对博客已经搭建并发布过文章的。</p><p>1、在你的博客仓库创建一个分支Hexo（这个命名随意）；</p><p>2、设置Hexo为默认分支（不知道怎么设的可以百度）；</p><p>3、将博客仓库clone至本地，将之前的Hexo文件夹中的<br> <code>_config.yml</code>，<code>themes/</code>，<code>source</code>，<code>scffolds/</code>，<code>package.json</code>，<code>.gitignore</code>复制到你克隆下来的仓库文件夹，即Username.github.io；（Username是你自己的用户名）</p><p>4、将themes/next/(我用的是NexT主题)中的<code>.git/</code>删除，否则无法将主题文件夹push；</p><p>5、在Username.github.io；文件夹执行<code>npm install</code>，<code>npm install hexo-deployer-git</code>(这里可以看看分支是不是显示为Hexo)</p><p><img src="https:////upload-images.jianshu.io/upload_images/4904768-2d12049be9999009.png?imageMogr2/auto-orient/strip|imageView2/2/w/476/format/webp" alt="img"></p><p>示例01.png</p><p>6、执行<code>git add</code>，<code>git commit -m &quot;提交文件&quot;</code>，<code>git push origin Hexo</code>来提交Hexo网站源文件；</p><blockquote><p><strong>注意</strong>：</p><p><img src="https:////upload-images.jianshu.io/upload_images/4904768-fd5424ef9b3d3235.png?imageMogr2/auto-orient/strip|imageView2/2/w/564/format/webp" alt="img"></p><p>示例02.png</p></blockquote><p>7、执行hexo g -d 生成静态网页部署到github上。<br> 这样，<a href="https://links.jianshu.com/go?to=https%3A%2F%2Folivivian.github.io%2F" target="_blank" rel="noopener">Username.github.io</a>仓库就有master分支保存静态网页，hexo分支保存源文件。</p><h3 id="修改"><a href="#修改" class="headerlink" title="修改"></a>修改</h3><p>在本地对博客修改（包括修改主题样式、发布新文章等）后</p><p>1、执行<code>git add</code>，<code>git commit -m &quot;提交文件&quot;</code>，<code>git push origin Hexo</code>来提交Hexo网站源文件；</p><p>2、执行hexo g -d 生成静态网页部署到github上；<br> （每次发布重复这两步，它们之间没有严格的顺序）</p><h3 id="恢复"><a href="#恢复" class="headerlink" title="恢复"></a>恢复</h3><p>换电脑想改博客：<br> 1、安装git；<br> 2、安装Nodejs和npm；<br> 3、使用克隆命令将仓库拷贝至本地；<br> 4、在文件夹内执行命令<code>npm install hexo-cli -g</code>、<code>npm install</code>、<code>npm install hexo-deployer-git</code>；</p><h3 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h3><p>Hexo的源文件说明：<br> 1、<code>_config.yml</code>站点的配置文件，需要拷贝；<br> 2、<code>themes/</code>主题文件夹，需要拷贝；<br> 3、<code>source</code>博客文章的.md文件，需要拷贝；<br> 4、<code>scaffolds/</code>文章的模板，需要拷贝；<br> 5、<code>package.json</code>安装包的名称，需要拷贝；<br> 6、<code>.gitignore</code>限定在push时哪些文件可以忽略，需要拷贝；<br> 7、<code>.git/</code>主题和站点都有，标志这是一个git项目，不需要拷贝；<br> 8、<code>node_modules/</code>是安装包的目录，在执行npm install的时候会重新生成，不需要拷贝；<br> 9、<code>public</code>是hexo g生成的静态网页，不需要拷贝；<br> 10、<code>.deploy_git</code>同上，hexo g也会生成，不需要拷贝；<br> 11、<code>db.json</code>文件，不需要拷贝。</p><p>不需要拷贝的文件正是.gitignore中所忽略的。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>在github上直接创建分支的方法，直接输入新的分支名，回车即可。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;本文转载自&lt;a href=&quot;https://www.jianshu.com/p/baab04284923&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;怎么去备份你的Hexo博客&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; clas
      
    
    </summary>
    
      <category term="博客" scheme="http://yoursite.com/categories/%E5%8D%9A%E5%AE%A2/"/>
    
    
      <category term="备忘" scheme="http://yoursite.com/tags/%E5%A4%87%E5%BF%98/"/>
    
  </entry>
  
  <entry>
    <title>深入理解kafka读书笔记</title>
    <link href="http://yoursite.com/2019/11/02/kafka-1-%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/"/>
    <id>http://yoursite.com/2019/11/02/kafka-1-基本概念/</id>
    <published>2019-11-02T11:43:08.668Z</published>
    <updated>2019-07-06T09:47:37.622Z</updated>
    
    <content type="html"><![CDATA[<h3 id="kafka-读书笔记"><a href="#kafka-读书笔记" class="headerlink" title="kafka 读书笔记"></a>kafka 读书笔记</h3><h4 id="一-术语"><a href="#一-术语" class="headerlink" title="一.术语"></a>一.术语</h4><p>​    <img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/mq/kafka/kafka.jpg" alt="123"></p><ul><li><p>Producer:生产者，也就是发送消息的一方。生产者负责创建消息，然后将其投递到Kafka中</p></li><li><p>Consumer:消费者,也就是接收消息的一方。消费者连接到Kafka上并接收消息，进而进行相应的业务处理。</p></li><li><p>Broker:服务代理节点。对于kafka而言，Broker可以简单的看做一个独立的kafka服务节点或Kafka服务实例。大多数情况下可以将Broker看作一台Kafka服务器，前提是这台服务器上只部署了一个Kafka实例。一个或多个Broker组成了一个Kafka集群。</p></li><li><p>在Kafka中还有两个重要的概念——主题（Topic）与分区(Partition)。Kafka中的消息以主题为单位进行归类，生产者负责将消息发送到特定的主题，而消费者负责订阅主题进行消费。</p><ul><li><p>主题是一个逻辑上的概念，他可以细分多个分区，<strong>一个分区只属于单个主题</strong>。</p></li><li><p><strong>同一主题下的不同分区包含的消息是不同的</strong>.分区在存储层面可以看做一个可追加的日志文件，消息被分区追加到分区日志文件的时候会分配一个特定的偏移量（offset）。offset是消息在分区中的唯一标识，Kafka通过它来保证消息在分区内的顺序性，不过offset不跨越分区，也就是说Kafka保证分区有序而不是主题有序。</p></li><li><p>如图所示，主题中有4个分区，消息被顺序追加到每个日志尾部。Kafka中的分区可以分布在不同的服务器上(broker),也就是说一个主题可以横跨多个broker.以此来提供比单个broker更强大的性能。</p><p> <img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/mq/kafka/message-write.jpg" alt="123"></p><p>每一条消息被发送到 broker 之前，会根据分区规则选择存储到哪个具体的分区 。 如果分区规则设定得合理，所有的消息都可以均匀地分配到不同的分区中 。 如果一个主题只对应一个文件，那么这个文件所在的机器 I/O 将会成为这个主题的性能瓶颈，而分区解决了这个问题 。 在创建主题的时候可以通过指定的参数来设置分区的个数，当然也可以在主题创建完成之后去修改分区的数量，通过增加分区的数量可以实现水平扩展。 </p><p>Kafka 为分区引入了多副本 （ Replica ） 机制， 通过增加副本数量可以提升容灾能力。同一分区的不同副本中保存的是相同的消息（在同一时刻，副本之间并非完全一样），副本之间是“ 一主多 从”的关系，其中 leader 副本负 责处理读写请求 ， follower 副本只负 责与 lead er 副本的消息同步。副本处于不同的 broker 中 ，当 leader 副本出现故障时，从 follower 副本中重新选举新的 leader 副本对外提供服务。 <strong>Kafka 通过多副本机制实现了故障的自动转移，当 Kafka 集群中某个 broker 失效时仍然能保证服务可用</strong> 。</p><p>如图 3 所示， Kafka 集群中有 4 个 broker，某个主题中有 3 个分区，且副本因子（即副本个数〉也为 3 ，如此每个分区便有 l 个 leader 副本和 2 个 follower 副本。生产者和消费者只与 leader副本进行交互，而 follower 副本只负责消息的同步，很多时候 follower 副本中的消息相对 leader副本而言会有一定的滞后。 </p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/mq/kafka/multi.jpg" alt="123"></p><p>Kafka 消费端也具备一 定 的 容灾能力。 Consumer 使用拉 （ Pull ）模式从服务端拉取消息，并且保存消费 的具体位置 ， 当消费者看机后恢复上线时可以根据之前保存的消费位置重新拉取需要的消息进行消 费 ，这样就不会造成消息丢失 。</p><p>分区中 的所有副本统称为 AR ( Assigned Replicas ） 。 <strong>所有与 leader 副本保持一定程度同步的副本（包括 leader 副本在内〕组成 ISR （In-Sync Replicas )</strong> , ISR 集合是 AR 集合中 的一个子集 。 消息会先发送到leader 副本，然后 follower 副本才能从 leader 副本中拉取消息进行同步，同步期间内 follower 副本相对于leader 副本而言会有一定程度的滞后 。 前面所说的“一定程度的同步”是指可忍受的滞后范围，这个范围可以通过参数进行配置 。 <strong>与 leader 副本同步滞后过多的副本（不包括 leader 副本）组成 OSR ( Out-of-Sync Replicas ）</strong>，由此可见， AR=ISR+OSR 。在正常情况下， 所有的 follower 副本都应该与 leader 副本保持一定程度 的同步，即 AR=ISR,OSR 集合为空。 </p><p>leader 副本负 责维护和跟踪 ISR 集合中所有 follower 副 本 的滞后状态， 当 follower 副本落后太多或失效时， leader 副本会把它从 ISR 集合中剔除 。 如果 OSR 集合中有 follower 副本 “追上’了 leader 副本，那么 leader 副本会把它从 OSR 集合转移至 ISR 集合 。 默认情况下， 当 leader 副本发生故障时，只 有在 ISR 集合中的副本才有资格被选举为新的 leader， 而在 OSR 集合中的副本则没有任何机会（不过这个原则也可以通过修改相应的参数配置来改变）。</p><p>ISR 与 HW 和 LEO 也有紧密的关系 。 HW 是 High Watermark 的缩写，俗称高水位，它标识了 一个特定 的消息偏移量（ offset ），<strong>消费者只能拉取到这个 offset 之前的消息</strong> 。 </p><p>如图 4 所示，它代表一个日志文件，这个日志文件中有 9 条消息，第一条消息的 offset (LogStartOffset)为0，最后一条的offset为8，offset为9的消息用虚线框标识，代表下一条待写入的消息。日志文件的HW为6，标识消费者只能拉取到offset在0到5之间的消息，而offset为6的消息对消费者而言是不可见的。 </p></li></ul></li></ul><pre><code>![123](&lt;https://raw.githubusercontent.com/mzl1989325/ImageBed/master/mq/kafka/offset.jpg&gt;) </code></pre><p>  ​                                                                       图4 分区中各种偏移量说明</p><p>  ​      LEO是LogEndOffset的缩写，它标识当前日志文件中下一条待写入消息的offset,图4中offset为9的位置即为当前日志文件的LEO，LEO的大小相当于当前日志分区中最后一条消息的offset值加1.分区ISR集合中的每个副本都会委会自身的LEO，而ISR集合中最小的LEO即为分区的HW,对于消费者而言只能消费HW之前的消息。</p><p>  ​     如下图所示，加入某个分区的ISR集合找那个有3个副本，即一个leader副本和2个follower副本，此时分区的LEO和HW都为3。消息3和消息4从生产者发出之后会被先存入leader副本，如图6所示。</p><p>  <img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/mq/kafka/wirte-1.jpg" alt="123"></p><p>   <img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/mq/kafka/write-2.jpg" alt="123"></p><p>​    在消息写入leader副本之后，follower副本会发送拉去请求来拉去消息3和消息4以进行消息同                        步。</p><p>​       在同步过程中，不同的 follower 副本的同步效率也不尽相同。如图 7 所示， 在某一时刻<br>follower1 完全跟上了 leader 副本而 follower2 只同步了消息 3 ，如此 leader 副本的 LEO 为 5,<br>follower1 的 LEO 为 5 , follower2 的 LEO 为 4 ， 那么当前分区的 HW 取最小值 4 ，此时消费者可<br>以消 费到 offset 为 0 至 3 之间的消息。  </p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/mq/kafka/write-3.jpg" alt="123"></p><p>​    如图8所示，所有的副本都成功写入了消息3和消息4，整个分区的HW和LEO都变为5，因此消费者可以消费到offset为4的消息了。</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/mq/kafka/write-4.jpg" alt="123"></p><p>由此可见， Kafka 的复制机制既不是完全的同步复制，也不是单纯的异步复制。事实上，<br>同步复制要求所有能工作的 follower 副本都复制完，这条消息才会被确认为已成功提交，这种<br>复制方式极大地影响了性能。而在异步复制方式下， follower 副本异步地从 leader 副本中 复制数<br>据，数据只要被 leader 副本写入就被认为已经成功提交。在这种情况下，如果 follower 副本都<br>还没有复制完而落后于 leader 副本，突然 leader 副本着机，则会造成数据丢失。 Kafka 使用的这<br>种 ISR 的方式则有效地权衡了数据可靠性和性能之间的关系。 </p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;kafka-读书笔记&quot;&gt;&lt;a href=&quot;#kafka-读书笔记&quot; class=&quot;headerlink&quot; title=&quot;kafka 读书笔记&quot;&gt;&lt;/a&gt;kafka 读书笔记&lt;/h3&gt;&lt;h4 id=&quot;一-术语&quot;&gt;&lt;a href=&quot;#一-术语&quot; class=&quot;head
      
    
    </summary>
    
      <category term="中间件" scheme="http://yoursite.com/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
      <category term="mq" scheme="http://yoursite.com/tags/mq/"/>
    
      <category term="kafka" scheme="http://yoursite.com/tags/kafka/"/>
    
  </entry>
  
  <entry>
    <title>java并发编程的艺术读书笔记（volatile）</title>
    <link href="http://yoursite.com/2019/11/02/java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E7%9A%84%E8%89%BA%E6%9C%AF%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-volatile/"/>
    <id>http://yoursite.com/2019/11/02/java并发编程的艺术读书笔记-volatile/</id>
    <published>2019-11-02T11:43:08.646Z</published>
    <updated>2019-08-31T09:43:49.906Z</updated>
    
    <content type="html"><![CDATA[<h2 id="java-并发编程的艺术读书笔记（volatile）"><a href="#java-并发编程的艺术读书笔记（volatile）" class="headerlink" title="java 并发编程的艺术读书笔记（volatile）"></a>java 并发编程的艺术读书笔记（volatile）</h2><h3 id="volatile的内存语义"><a href="#volatile的内存语义" class="headerlink" title="volatile的内存语义"></a>volatile的内存语义</h3><h4 id="一-volatile的特性"><a href="#一-volatile的特性" class="headerlink" title="一.volatile的特性"></a>一.volatile的特性</h4><ol><li><strong>可见性</strong>：对一个volatile变量的读，总是能看到（任意线程）对这个volatile变量最后的写入。</li><li><strong>原子性</strong>：对任意单个volatile变量的读/写具有原子性，但类似于volatile++这种复合操作不具有原子性。</li></ol><h4 id="二-volatile写-读建立的happens-before关系"><a href="#二-volatile写-读建立的happens-before关系" class="headerlink" title="二.volatile写-读建立的happens-before关系"></a>二.volatile写-读建立的happens-before关系</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">class VolatileExample &#123;</span><br><span class="line">    int a = 0;</span><br><span class="line">    volatile boolean flag = false;</span><br><span class="line">    public void writer() &#123;</span><br><span class="line">        a = 1; // 1</span><br><span class="line">        flag = true; // 2</span><br><span class="line">    &#125; </span><br><span class="line">    public void reader() &#123;</span><br><span class="line">        if (flag) &#123; // 3</span><br><span class="line">            int i = a; // 4</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>假设线程A执行writer()方法之后，线程B执行reader()方法。根据happens-before规则，这个过程建立的happens-before关系可以分为3类： </p><ol><li>根据程序次序规则，1 happens-before 2;3 happens-before 4 </li><li>根据程序次序规则，1 happens-before 2;3 happens-before 4 </li><li>根据happens-before的传递性规则，1 happens-before 4</li></ol><h4 id="三-volatile写-读的内存语义"><a href="#三-volatile写-读的内存语义" class="headerlink" title="三.volatile写-读的内存语义"></a>三.volatile写-读的内存语义</h4><p><strong>当写一个volatile变量时，JMM会把该线程对应的本地内存中的共享变量值刷新到主内存 </strong></p><h4 id="四-volatile读的内存语义如下："><a href="#四-volatile读的内存语义如下：" class="headerlink" title="四.volatile读的内存语义如下："></a>四.volatile读的内存语义如下：</h4><p><strong>当读一个volatile变量时，JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量 </strong></p><h4 id="五-volatile内存语义的实现"><a href="#五-volatile内存语义的实现" class="headerlink" title="五.volatile内存语义的实现"></a>五.volatile内存语义的实现</h4><p>为了实现volatile的内存语义，编译器在生成字节码时，会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说，发现一个最优布置来最小化插入屏障的总数几乎不可能。为此，JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略 。</p><ul><li>在每个volatile写操作的前面插入一个StoreStore屏障 。</li><li>在每个volatile写操作的后面插入一个StoreLoad屏障 。</li><li>在每个volatile读操作的后面插入一个LoadLoad屏障 。</li><li>在每个volatile读操作的后面插入一个LoadStore屏障 。</li></ul><p>volatile写插入内存屏障后生成的指令序列示意图 </p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/threads/volatile1.jpg" alt></p><p>volatile读插入内存屏障后生成的指令序列示意图 </p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/threads/volatile2.jpg" alt></p><p>图中的StoreStore屏障可以保证在volatile写之前，其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。</p><p>这里比较有意思的是，volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面是否需要插入一个StoreLoad屏障（比如，一个volatile写之后方法立即return）。为了保证能正确实现volatile的内存语义，JMM在采取了保守策略：在每个volatile写的后面，或者在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑，JMM最终选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是：一个<br>写线程写volatile变量，多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时，选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里可以看到JMM在实现上的一个特点：首先确保正确性，然后再去追求执行效率。</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/threads/volatile3.jpg" alt="volatile3"></p><p>图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。</p><p>上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时，只要不改变volatile写-读的内存语义，编译器可以根据具体情况省略不必要的屏障。下面通过具体的示例代码进行说明 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">class VolatileBarrierExample &#123;</span><br><span class="line">int a;</span><br><span class="line">volatile int v1 = 1;</span><br><span class="line">volatile int v2 = 2;</span><br><span class="line">void readAndWrite() &#123;</span><br><span class="line">int i = v1; // 第一个volatile读</span><br><span class="line">int j = v2; // 第二个volatile读</span><br><span class="line">a = i + j; // 普通写</span><br><span class="line">v1 = i + 1; // 第一个volatile写</span><br><span class="line">v2 = j * 2; // 第二个 volatile写</span><br><span class="line">&#125; …</span><br><span class="line">// 其他方法</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意，最后的StoreLoad屏障不能省略。因为第二个volatile写之后，方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写，为了安全起见，编译器通常会在这里插入一个StoreLoad屏障。</p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/threads/volatile4.jpg" alt></p><p>上面的优化针对任意处理器平台，由于不同的处理器有不同“松紧度”的处理器内存模型，内存屏障的插入还可以根据具体的处理器内存模型继续优化。以X86处理器为例，上图中除最后的StoreLoad屏障外，其他的屏障都会被省略 。</p><p>前面保守策略下的volatile读和写，在X86处理器平台可以优化成如下图所示。 </p><p>X86处理器仅会对写-读操作做重排序。X86不会对读-读、读-写和写-写操作做重排序，因此在X86处理器中会省略掉这3种操作类型对应的内存屏障。在X86中，JMM仅需在volatile写后面插入一个StoreLoad屏障即可正确实现volatile写-读的内存语义。这意味着在X86处理器中，volatile写的开销比volatile读的开销会大很多（因为执行StoreLoad屏障开销会比较大） </p><p><img src="https://raw.githubusercontent.com/mzl1989325/ImageBed/master/threads/volatile5.jpg" alt="volatile5"></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;java-并发编程的艺术读书笔记（volatile）&quot;&gt;&lt;a href=&quot;#java-并发编程的艺术读书笔记（volatile）&quot; class=&quot;headerlink&quot; title=&quot;java 并发编程的艺术读书笔记（volatile）&quot;&gt;&lt;/a&gt;java 并发编
      
    
    </summary>
    
      <category term="多线程" scheme="http://yoursite.com/categories/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
    
      <category term="JMM" scheme="http://yoursite.com/tags/JMM/"/>
    
      <category term="读书笔记" scheme="http://yoursite.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
      <category term="volatile" scheme="http://yoursite.com/tags/volatile/"/>
    
  </entry>
  
  <entry>
    <title>Hello World</title>
    <link href="http://yoursite.com/2019/11/02/hello-world/"/>
    <id>http://yoursite.com/2019/11/02/hello-world/</id>
    <published>2019-11-02T11:43:08.621Z</published>
    <updated>2019-07-06T09:06:48.540Z</updated>
    
    <content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="noopener">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html" target="_blank" rel="noopener">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="noopener">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><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">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html" target="_blank" rel="noopener">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><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">$ hexo server</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html" target="_blank" rel="noopener">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><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">$ hexo generate</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html" target="_blank" rel="noopener">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><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">$ hexo deploy</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/deployment.html" target="_blank" rel="noopener">Deployment</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Welcome to &lt;a href=&quot;https://hexo.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hexo&lt;/a&gt;! This is your very first post. Check &lt;a href=&quot;https://hexo.
      
    
    </summary>
    
    
  </entry>
  
</feed>
