<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Etcd on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/etcd/</link>
    <description>Recent content in Etcd on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Mon, 28 Jun 2021 17:01:53 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/etcd/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Etcd Raft库的日志存储</title>
      <link>https://www.codedump.info/zh/post/20210628-etcd-wal/</link>
      <pubDate>Mon, 28 Jun 2021 17:01:53 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20210628-etcd-wal/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;之前看etcd raft实现的时候，由于wal以及日志的落盘存储部分，没有放在raft模块中，对这部分没有扣的特别细致。而且，以前我的观点认为etcd raft把WAL这部分留给了上层的应用去实现，自身通过&lt;code&gt;Ready&lt;/code&gt;结构体来通知应用层落盘的数据，这个观点也有失偏颇，etcd只是没有把这部分代码放在raft模块中，属于代码组织的范畴问题，并不是需要应用层自己来实现。&lt;/p&gt;&#xA;&lt;p&gt;于是，决定专门写一篇文章把这部分内容给讲解一下，主要涉及以下内容：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;日志（包括快照）文件的格式。&lt;/li&gt;&#xA;&lt;li&gt;日志（包括快照）内容的落盘、恢复。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以前的系列文章可以在下面的链接中找到，本文不打算过多重复原理性的内容：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180921-raft/&#34;&gt;Raft算法原理&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180922-etcd-raft/&#34;&gt;etcd Raft库解析&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20181125-etcd-server/&#34;&gt;Etcd存储的实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20210515-raft/&#34;&gt;Etcd Raft库的工程化实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;wal及快照文件格式&#34;&gt;&#xA;  WAL及快照文件格式&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#wal%e5%8f%8a%e5%bf%ab%e7%85%a7%e6%96%87%e4%bb%b6%e6%a0%bc%e5%bc%8f&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;首先来讲解这两种文件的格式，了解了格式才能继续展开下面的讲述。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;wal文件格式&#34;&gt;&#xA;  WAL文件格式&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#wal%e6%96%87%e4%bb%b6%e6%a0%bc%e5%bc%8f&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;wal文件的文件名格式为：seq-index.wal（见函数&lt;code&gt;walName&lt;/code&gt;）。其中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;seq：序列号，从0开始递增。&lt;/li&gt;&#xA;&lt;li&gt;index：该wal文件存储的第一条日志数据的索引。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因此，如果将一个目录下的所有wal文件按照名称排序之后，给定一个日志索引，很快就能知道该索引的日志落在哪个wal文件之中的。&lt;/p&gt;&#xA;&lt;p&gt;WAL文件中每条记录的格式如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-proto&#34; data-lang=&#34;proto&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Record&lt;/span&gt; {&lt;span style=&#34;&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;&#34;&gt;&lt;/span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;optional&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;int64&lt;/span&gt; type  = 1 [(gogoproto.nullable) = &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;false&lt;/span&gt;];&lt;span style=&#34;&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;&#34;&gt;&lt;/span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;optional&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uint32&lt;/span&gt; crc  = 2 [(gogoproto.nullable) = &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;false&lt;/span&gt;];&lt;span style=&#34;&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;&#34;&gt;&lt;/span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;optional&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;bytes&lt;/span&gt; data  = 3;&lt;span style=&#34;&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;&#34;&gt;&lt;/span&gt;}&lt;span style=&#34;&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;type：记录的类型，下面解释。&lt;/li&gt;&#xA;&lt;li&gt;crc：后面data部分数据的crc32校验值。&lt;/li&gt;&#xA;&lt;li&gt;data：数据部分，根据类型的不同有不同格式的数据。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;记录数据的类型如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;const&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 以下是WAL存放的数据类型&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 元数据&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;metadataType &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;int64&lt;/span&gt; = &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;iota&lt;/span&gt; + 1&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 日志数据&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;entryType&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 状态数据&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;stateType&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 校验初始值&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;crcType&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 快照数据&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;snapshotType&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;下面展开解释。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Etcd Raft库的工程化实现</title>
      <link>https://www.codedump.info/zh/post/20210515-raft/</link>
      <pubDate>Sat, 15 May 2021 13:52:08 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20210515-raft/</guid>
      <description>&lt;p&gt;最近回顾前几年写的Raft、etcd raft的实现文章，以及重新阅读Raft论文、etcd raft代码，发现之前有些理解不够准确、深刻，但是不打算在原文上做修正，于是写这篇补充的文章做一些另外角度的解释，以前的系列文章可以在下面的链接中找到，本文不打算过多重复原理性的内容：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180921-raft/&#34;&gt;Raft算法原理&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180922-etcd-raft/&#34;&gt;etcd Raft库解析&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20181125-etcd-server/&#34;&gt;Etcd存储的实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在开始展开讨论前，先介绍这个Raft论文中的示意图，我认为能理解这幅图才能对一致性算法有个全貌的了解：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;Etcd Raft与应用层的交互&#34; src=&#34;https://www.codedump.info/media/imgs/20210515-raft/statemachine.jpeg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; Etcd Raft与应用层的交互 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;图中分为两种进程：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;server进程：server进程中运行着一致性算法模块、持久化保存的日志、以及按照日志提交的顺序来进行顺序操作的状态机。&lt;/li&gt;&#xA;&lt;li&gt;client进程：用于向server提交日志的进程。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;需要说明的是，两种进程都用叠加的矩形来表示，意指系统中这两类进程不止一个。&lt;/p&gt;&#xA;&lt;p&gt;一个日志要被正确的提交，图中划分了几步：&lt;/p&gt;&#xA;&lt;p&gt;1、client进程提交数据到server进程，server进程将收到的日志数据灌入一致性模块。&lt;/p&gt;&#xA;&lt;p&gt;2、一致性模块将日志写入本地WAL，然后同步给集群中其他server进程。&lt;/p&gt;&#xA;&lt;p&gt;3、多个节点对某条日志达成一致之后，将修改本地的提交日志索引（commit index）；落盘后的日志按照顺序灌入状态机，只要保证所有server进程上的日志顺序，那么最后状态机的状态肯定就是一致的了。&lt;/p&gt;&#xA;&lt;p&gt;4、灌入状态机之后，server进程可以应答客户端。&lt;/p&gt;&#xA;&lt;p&gt;所以，本质上，一个使用了一致性算法的库，划分了成了两个不同的模块：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一致性算法库，这里泛指Raft、Paxos、Zab等一致性协议。这类一致性算法库主要做如下的事情：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;用户输入库中日志（log），由库根据各自的算法来检测日志的正确性，并且通知上层的应用层。&#xA;&lt;ul&gt;&#xA;&lt;li&gt;输入到库中的日志维护和管理，算法库中需要知道哪些日志提交、提交成功、以及上层的应用层已经applied过的。当发生错误的时候，某些日志还会进行回滚（rollback）操作。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;日志的网络收发，这部分属于可选功能。有一些库，比如braft把这个事情也揽过来自己做了，优点是使用者不需要关注这部分功能，缺点是braft和它自带的网络库brpc耦合的很紧密，不可能拆开来使用；另一些raft实现，比如这里重点提到etcd raft实现，并不自己完成网络数据收发的工作，而是通知应用层，由应用层自己实现。&lt;/li&gt;&#xA;&lt;li&gt;日志的持久化存储：这部分也属于可选功能。前面说过，一致性算法库中维护了未达成一致的日志缓冲区，达成一致的日志才通知应用层，因此在这里不同的算法库又有了分歧，braft也是自己完成了日志持久化的工作，etcd raft则是将这部分工作交给了应用层。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;应用层：即工作在一致性算法之上的库使用者，这个就比上图中的“状态机”：只有达成一致并且落盘的数据才灌入应用层，只要保证灌入应用层的日志顺序一致那么最后的状态就是一致的。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;总体来看，一个一致性算法库有以下必选和可选功能：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;输入日志进行处理的算法（必选）。&lt;/li&gt;&#xA;&lt;li&gt;日志的维护和管理（必选）。&lt;/li&gt;&#xA;&lt;li&gt;日志（包括快照）数据的网络收发（可选）。&lt;/li&gt;&#xA;&lt;li&gt;日志（包括快照）的持久化存储（可选）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;需要特别说明的是，即便是后面两个工作是可选的，但是可选还是必选的区别在于，这部分工作是一致性算法库自己完成，还是由算法库通知给上面的应用层去完成，并不代表这部分工作可以完全不做。&lt;/p&gt;&#xA;&lt;p&gt;在下表中列列举了etcd raft和braft在这几个特性之间的区别：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;功能&lt;/th&gt;&#xA;          &lt;th&gt;etcd raft&lt;/th&gt;&#xA;          &lt;th&gt;braft&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;raft一致性算法&lt;/td&gt;&#xA;          &lt;td&gt;实现&lt;/td&gt;&#xA;          &lt;td&gt;实现&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;日志的维护和管理&lt;/td&gt;&#xA;          &lt;td&gt;实现&lt;/td&gt;&#xA;          &lt;td&gt;实现&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;日志数据的网络收发&lt;/td&gt;&#xA;          &lt;td&gt;交由应用层&lt;/td&gt;&#xA;          &lt;td&gt;自己实现&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;日志数据的持久化存储&lt;/td&gt;&#xA;          &lt;td&gt;交由应用层&lt;/td&gt;&#xA;          &lt;td&gt;自己实现&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;优缺点&lt;/td&gt;&#xA;          &lt;td&gt;松耦合，易于验证、测试；需要应用者做更多的事情&lt;/td&gt;&#xA;          &lt;td&gt;与其rpc库紧耦合，难拆分；应用层做的事情不多，易于用来做服务&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;两种实现各有自己的优缺点，braft类实现更适合提供一个需要集成raft的服务时，可以直接用来实现服务；etcd raft类的实现，由于与网络、存储层耦合不紧密，易于进行测试，更适合拿来做为库使用。&lt;/p&gt;&#xA;&lt;p&gt;如果把前面的一致性算法的几个特性做一个抽象，我认为一致性算法库本质上就是一个“维护操作日志的算法库，只要大家都按照相同的顺序将日志灌入应用层”就好，其工作原理大体如下图：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;一致性算法的本质&#34; src=&#34;https://www.codedump.info/media/imgs/20210515-raft/co-algo.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 一致性算法的本质 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如果把问题抽象成这样的话，那么本质上，所谓的“一致性算法库”跟一个经常看到的tcp、kcp甚至是一个应用层的协议栈也就没有什么区别了：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大家都要维护一个数据区：只有确认过正确的，才会抛给上一层。以TCP协议算法来说，比如发送但未确认的数据由协议栈的缓冲区维护，如果超时还未等到对端的确认，将发起超时重传等，这些都是每种协议算法的具体细节，但是本质上这些协议都要维护一个未确认数据的缓冲区。一致性算法在数据的维护上会更复杂一些，一是参与确认的节点不止通信的C/S两端，需要集群中半数以上节点的确认；同时，在未确认之前日志需要首先落盘，在提交成功之后再抛给应用层。&lt;/li&gt;&#xA;&lt;li&gt;只要保证所有参与的节点，都以相同的数据灌入日志给应用层，那么得到的结果将最终一致。&lt;/li&gt;&#xA;&lt;li&gt;确认的流程是可以pipeline异步化的，提交日志的进程并不需要一直等待日志被提交成功，而是提交之后等待。不妨以下面的流程来做解释：&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;流水线异步化的日志提交流程&#34; src=&#34;https://www.codedump.info/media/imgs/20210515-raft/pipeline.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 流水线异步化的日志提交流程 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;其中：&lt;/p&gt;</description>
    </item>
    <item>
      <title>etcd Raft库解析</title>
      <link>https://www.codedump.info/zh/post/20180922-etcd-raft/</link>
      <pubDate>Sat, 22 Sep 2018 11:01:02 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20180922-etcd-raft/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;本文是博客解析raft算法及etcd raft库实现的系列三篇文章之一，之所以详细结合etcd实现解析raft算法原理及实现，因为etcd的raft实现是最接近论文本身的，结合论文原理一起阅读十分酸爽。这个系列文章的索引如下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180921-raft/&#34;&gt;Raft算法原理&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180922-etcd-raft/&#34;&gt;etcd Raft库解析&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20181125-etcd-server/&#34;&gt;Etcd存储的实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20210515-raft/&#34;&gt;Etcd Raft库的工程化实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;另外，我个人还针对etcd 3.1.10版本的raft相关代码实现做了一些代码的注释笔记，地址在此：&lt;a href=&#34;https://github.com/lichuang/etcd-3.1.10-codedump&#34;&gt;etcd-3.1.10-codedump&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;序言&#34;&gt;&#xA;  序言&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%ba%8f%e8%a8%80&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;今年初开始学习了解Raft协议，论文读下来之后还是决定结合一个成熟的代码进行更深的理解。etcd做为一个非常成熟的作品，其Raft库实现也非常精妙，屏蔽了网络、存储等模块，提供接口由上层应用者来实现。&lt;/p&gt;&#xA;&lt;p&gt;本篇文章解析etcd的Raft库实现，基于etcd 3.1.10版本。etcd的Raft库，位于其代码目录的Raft中。&lt;/p&gt;&#xA;&lt;p&gt;我自己也单独将3.1.10的代码拉出了一个专门添加了我阅读代码注释的版本，目前Raft这部分基本都做了注释，见：&#xA;&lt;a href=&#34;https://github.com/lichuang/etcd-3.1.10-codedump&#34;&gt;https://github.com/lichuang/etcd-3.1.10-codedump&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;以下在介绍的时候，可能会混用中文和英文术语，这里先列举出来：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;英文&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;中文&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Term&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;选举任期，每次选举之后递增1&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Vote&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;选举投票(的ID)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Entry&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;Raft算法的日志数据条目&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;candidate&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;候选人&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;leader&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;领导者&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;follower&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;跟随者&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;commit&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;提交&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;propose&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;提议&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;输入及输出&#34;&gt;&#xA;  输入及输出&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%be%93%e5%85%a5%e5%8f%8a%e8%be%93%e5%87%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;既然做为一个库使用，就有其确定的输入和输出接口，先来了解这部分再进行后续的展开讨论。&lt;/p&gt;&#xA;&lt;p&gt;作为一个一致性算法的库，不难想象使用的一般场景是这样的：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;应用层接收到新的写入数据请求，向该算法库写入一个数据。&lt;/li&gt;&#xA;&lt;li&gt;算法库返回是否写入成功。&lt;/li&gt;&#xA;&lt;li&gt;应用层根据写入结果进行下一步的操作。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;然而，Raft库却相对而言更复杂一些，因为还有以下的问题存在：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;写入的数据，可能是集群状态变更的数据，Raft库在执行写入这类数据之后，需要返回新的状态给应用层。&lt;/li&gt;&#xA;&lt;li&gt;Raft库中的数据不可能一直以日志的形式存在，这样会导致数据越来越大，所以有可能被压缩成快照（snapshot）的数据形式，这种情况下也需要返回这部分快照数据。&lt;/li&gt;&#xA;&lt;li&gt;由于etcd的Raft库不包括持久化数据存储相关的模块，而是由应用层自己来做实现，所以也需要返回在某次写入成功之后，哪些数据可以进行持久化保存了。&lt;/li&gt;&#xA;&lt;li&gt;同样的，etcd的Raft库也不自己实现网络传输，所以同样需要返回哪些数据需要进行网络传输给集群中的其他节点。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;以上的这些，集中在raft/node.go的Ready结构体中，其包括以下成员：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;成员名称&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;类型&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;作用&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;SoftState&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;SoftState&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;软状态，软状态易变且不需要保存在WAL日志中的状态数据，包括：集群leader、节点的当前状态&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;HardState&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;HardState&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;硬状态，与软状态相反，需要写入持久化存储中，包括：节点当前Term、Vote、Commit&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;ReadStates&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;[]ReadStates&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;用于读一致性的数据，后续会详细介绍&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Entries&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;[]pb.Entry&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;在向其他集群发送消息之前需要先写入持久化存储的日志数据&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Snapshot&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;pb.Snapshot&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;需要写入持久化存储中的快照数据&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;CommittedEntries&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;[]pb.Entry&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;需要输入到状态机中的数据，这些数据之前已经被保存到持久化存储中了&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Messages&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;[]pb.Message&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;在entries被写入持久化存储中以后，需要发送出去的数据&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;以上的成员说明，最开始看不一定能理解其含义和用法，不过在后续会慢慢展开讨论。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
