<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>读书笔记 on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</link>
    <description>Recent content in 读书笔记 on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Thu, 18 Apr 2019 08:40:34 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>《数据密集型应用系统设计》第九章《一致性与共识》笔记</title>
      <link>https://www.codedump.info/zh/post/20190406-ddia-chapter09-consistency-and-consensus/</link>
      <pubDate>Thu, 18 Apr 2019 08:40:34 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190406-ddia-chapter09-consistency-and-consensus/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;一致性保证&#34;&gt;&#xA;  一致性保证&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%80%e8%87%b4%e6%80%a7%e4%bf%9d%e8%af%81&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;最终一致性（eventual consistency）：如果停止更新数据，等待一段时间（时间长度未知），则最终所有读请求将返回相同的内容。&lt;/p&gt;&#xA;&lt;p&gt;然而最终一致性是一种非常弱的一致性保证，因为无法知道何时（when）系统会收敛。而在收敛之前，读请求都可能返回任何值。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;可线性化linearizability&#34;&gt;&#xA;  可线性化（Linearizability）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%8f%af%e7%ba%bf%e6%80%a7%e5%8c%96linearizability&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;可线性化（Lineariazability），也被称为原子一致性（atomic consistency）、强一致性（strong consistency），其基本的思想是让一个系统看起来好像只有一个数据副本，且所有的操作都是原子的。有了这个保证，应用程序不需要再关系系统内部有多少个副本。&lt;/p&gt;&#xA;&lt;p&gt;在一个可线性化的系统中，一旦客户端成功提交写请求，所有客户端的读请求一定能看到刚刚写入的值。这一保证让客户端认为只有一个副本，这样任何一次读取都能读到最新的值，而不是过期的数据。&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;9-1&#34; src=&#34;https://www.codedump.info/media/imgs/20190406-ddia-chapter09-consistency-and-consensus/9-1.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 9-1 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中，alice和bob同时等待2014年世界杯决赛的结果。在宣布最终比分之后，alice看到了最终的结果，然后将此结果告诉了bob，bob马上在自己的手机上刷新想看最新的结果，但是却返回了过期的数据，显示当前比赛还在进行中。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;如何实现可线性化&#34;&gt;&#xA;  如何实现可线性化？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%a6%82%e4%bd%95%e5%ae%9e%e7%8e%b0%e5%8f%af%e7%ba%bf%e6%80%a7%e5%8c%96&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&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;9-2&#34; src=&#34;https://www.codedump.info/media/imgs/20190406-ddia-chapter09-consistency-and-consensus/9-2.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 9-2 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在上图中，分为两种操作：针对某个值进行read和write操作。&lt;/p&gt;&#xA;&lt;p&gt;客户端A的第一次和最后一次read操作，分别返回0和1，这没有问题，因为在这两次操作中间有客户端C的write操作将数据x更新为了1。&lt;/p&gt;&#xA;&lt;p&gt;但是，在写操作还在进行的时候，如果读操作返回的值会来回的跳变，即某次读请求返回的是旧值，而某一次又返回的是新值，这对于一个可线性化系统而言是不可接受的。&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;9-3&#34; src=&#34;https://www.codedump.info/media/imgs/20190406-ddia-chapter09-consistency-and-consensus/9-3.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 9-3 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在上图中，箭头表示时序依赖关系。即先有客户端A的第二次read(x)操作，再有客户端B的第二次read(x)操作。客户端A的第二次读请求返回了x的新值1，而客户端B在这次读请求之后也去读x的值，此时应该返回的也是新值1。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;即：在一个可线性化的系统中，有一个很重要的约束条件，在写操作开始和结束之间必然存在一个时间段，此时读到x的值会在旧值与新值之间跳变。但是，如果某个客户端的读请求返回了新值，那么即使这时写操作还未真正完成，后续的所有读请求也应该返回新值。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;以下的例子进一步解释可线性化的操作，除了读写之外又引入另一种操作：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;cas(x, old, new)：表示一次原子的比较-设置操作（compare-and-set，简称CAS），如果此时x的值为old，则原子设置这个值为new；否则保留原有值不变，这个操作的返回值表示这次x原有的值是否为old，即设置操作是否发生。&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;9-4&#34; src=&#34;https://www.codedump.info/media/imgs/20190406-ddia-chapter09-consistency-and-consensus/9-4.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 9-4 &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;p&gt;在上图中，有一些细节需要注意：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;客户端B首先read(x)，接下来客户端D write(x,0)，然后客户端A在write(x,1)，而最终返回给客户端B的值是1（客户端A写入的值）。这个结果是可能的，这意味着数据库执行的顺序是：先处理客户端D的写请求，然后是A的写入操作，最后才是B的读请求。虽然这个顺序并不是上面请求的顺序，但是考虑到请求有网络延迟的情况，比如可能B的请求延迟很大，导致在两次写请求之后才打到数据库，因此只能返回最后A写入的值。&lt;/li&gt;&#xA;&lt;li&gt;客户端A在收到写请求的应答之前，B就收到了新的值1，这表明写入成功。这也是可能的，这并不意味着B的读请求在A的写请求之前发生，只是意味着由于网络延迟等原因导致A稍后才收到响应。&lt;/li&gt;&#xA;&lt;li&gt;客户端的最后一次读取不满足线性化。因为在此之前，A已经读到了由C进行cas(x,2,4)操作设置的新值4，B的最后一次读请求在A读取到4之后，因此B不能读到旧值2了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;注意可线性化（Lineariazability）和可串行化（Serializability）的区别：&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;数据库可以同时支持可串行化与可线性化，这种组合又被称为严格的可串行化或者强的单副本可串行化（strong one-copy Serializability）。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;线性化的依赖条件&#34;&gt;&#xA;  线性化的依赖条件&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%ba%bf%e6%80%a7%e5%8c%96%e7%9a%84%e4%be%9d%e8%b5%96%e6%9d%a1%e4%bb%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;实现线性化系统&#34;&gt;&#xA;  实现线性化系统&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%ae%9e%e7%8e%b0%e7%ba%bf%e6%80%a7%e5%8c%96%e7%b3%bb%e7%bb%9f&#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;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;无主复制（可能不可线性化）：对于无主节点复制的系统，依赖于具体的quorum配置，以及如何定义强一致性，可能并不能保证线性化。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;线性化与quorum&#34;&gt;&#xA;  线性化与quorum&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%ba%bf%e6%80%a7%e5%8c%96%e4%b8%8equorum&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&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;9-6&#34; src=&#34;https://www.codedump.info/media/imgs/20190406-ddia-chapter09-consistency-and-consensus/9-6.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 9-6 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中，x的初始值为0，写客户端向所有三副本（n=3，w=3）写入更新x为1。而客户端A从两个节点（r=2）读数据，其中一个节点返回1，而客户端B则从两个节点都得到了0。&lt;/p&gt;&#xA;&lt;p&gt;显然这是违反线性化要求的：客户端B在客户端A之后读取数据，但是仍然得到了旧值。&lt;/p&gt;&#xA;&lt;p&gt;总而言之，最安全的假定是类似Dynamo风格的无主复制系统无法保证线性化。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;线性化的代价&#34;&gt;&#xA;  线性化的代价&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%ba%bf%e6%80%a7%e5%8c%96%e7%9a%84%e4%bb%a3%e4%bb%b7&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;cap理论&#34;&gt;&#xA;  CAP理论&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#cap%e7%90%86%e8%ae%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;在一个数据中心内部，主要存在不可靠的网络，就可能会违背线性化的风险，需要做出权衡考虑：&lt;/p&gt;</description>
    </item>
    <item>
      <title>《数据密集型应用系统设计》第八章《分布式系统的挑战》笔记</title>
      <link>https://www.codedump.info/zh/post/20190405-ddia-chapter08-the-trouble-with-distributed-system/</link>
      <pubDate>Tue, 16 Apr 2019 21:03:16 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190405-ddia-chapter08-the-trouble-with-distributed-system/</guid>
      <description>&lt;p&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;#%e6%95%85%e9%9a%9c%e4%b8%8e%e9%83%a8%e5%88%86%e5%a4%b1%e6%95%88&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;单机上的程序，以一种确定性的方式运行：要么工作，要么出错。&lt;/p&gt;&#xA;&lt;p&gt;然而涉及到多台节点时，会出现系统的一部分正常，一部分异常的情况，称为“部分故障（partial failure）”。&lt;/p&gt;&#xA;&lt;p&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;#%e4%b8%8d%e5%8f%af%e9%9d%a0%e7%9a%84%e7%bd%91%e7%bb%9c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&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;li&gt;远程节点接收并且处理了请求，但是回复却丢失了。&lt;/li&gt;&#xA;&lt;li&gt;远程节点已经完成了请求，但是回复被延迟了。&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;8-1&#34; src=&#34;https://www.codedump.info/media/imgs/20190405-ddia-chapter08-the-trouble-with-distributed-system/8-1.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 8-1 &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;p&gt;从以上可以知道，异步网络中的消息没有得到响应，但是无法判断具体的原因。&lt;/p&gt;&#xA;&lt;p&gt;处理这种问题通常采用超时机制：在等待一段时间之后，如果没有收到回复则选择放弃，并且认为响应不会到达。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;检测网络故障&#34;&gt;&#xA;  检测网络故障&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a3%80%e6%b5%8b%e7%bd%91%e7%bb%9c%e6%95%85%e9%9a%9c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;如果超时是检测网络故障的唯一可行方法，那么这个超时时间应该如何选择？&lt;/p&gt;&#xA;&lt;p&gt;太小：出现误判的情况。太大：意味着要很长时间才能宣布节点失效了。&lt;/p&gt;&#xA;&lt;p&gt;假设有一个虚拟的系统，网络可以保证数据报在一个最大延迟范围内：要么在时间d内交付完成，要么丢失。此外，非故障节点在时间r内完成请求的处理。此时，就可以确定成功的请求总是在2d+r时间内完成，因此这个时间是一个理想超时时间。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;同步网络和异步网络&#34;&gt;&#xA;  同步网络和异步网络&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%90%8c%e6%ad%a5%e7%bd%91%e7%bb%9c%e5%92%8c%e5%bc%82%e6%ad%a5%e7%bd%91%e7%bb%9c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;既然同步网络可以在规定的延迟时间内完成数据的发送，且不会丢失数据包，那么为什么分布式系统没有选择同步网络，在硬件层面就解决网络问题？&lt;/p&gt;&#xA;&lt;p&gt;原因在于，固定电话网络中的电路与TCP连接存在很大的不同：电路方式总是预留固定带宽，在电路建立之后其他人无法使用；而TCP连接的数据包则会尝试使用所有可用的网络带宽。TCP可以传送任意大小可变的数据块，会尽力在最短时间内完成数据传送。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;不可靠的时钟&#34;&gt;&#xA;  不可靠的时钟&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%8d%e5%8f%af%e9%9d%a0%e7%9a%84%e6%97%b6%e9%92%9f&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;很多操作依赖时间，但是时间也是靠不住的，本节就是说这部分的内容。&lt;/p&gt;&#xA;&lt;p&gt;计算机的时钟分为两种，墙上时钟（time-of-day clock）和单调时钟（monotonic clock），但是两者在使用上是有区别的。&lt;/p&gt;&#xA;&lt;p&gt;墙上时钟根据某个日历（也称为墙上时间，wall-clock time）返回当前的日期和时间。比如Linux的系统调用clock_gettime(CLOCK_REALTIME)返回自1970年1月1日以来的秒数和毫秒数。&lt;/p&gt;&#xA;&lt;p&gt;单调时钟更适合用于测试持续时间段（时间间隔），Linux的系统调用clock_gettime(CLOCK_MONITONIC)返回的就是单调时钟。单调时钟的名字源于它们总是保证向前走而不会出现回拨现象。&lt;/p&gt;&#xA;&lt;p&gt;可以在一个时间点读取单调时钟的值，完成某项工作然后再次检查时钟，时钟之间的插值就是两次检查的时间间隔。&lt;/p&gt;&#xA;&lt;p&gt;但是，单调时钟的绝对值没有任何意义。&lt;/p&gt;&#xA;&lt;p&gt;单调时钟不需要同步，而墙上时钟需要根据NTP服务器或外部时间源做调整。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;依赖时钟的同步&#34;&gt;&#xA;  依赖时钟的同步&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%be%9d%e8%b5%96%e6%97%b6%e9%92%9f%e7%9a%84%e5%90%8c%e6%ad%a5&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;某些操作强依赖时钟的同步，这里往往容易出现问题，这一节就是列举这些问题。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;时间戳与事件顺序&#34;&gt;&#xA;  时间戳与事件顺序&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%97%b6%e9%97%b4%e6%88%b3%e4%b8%8e%e4%ba%8b%e4%bb%b6%e9%a1%ba%e5%ba%8f&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&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;8-3&#34; src=&#34;https://www.codedump.info/media/imgs/20190405-ddia-chapter08-the-trouble-with-distributed-system/8-3.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 8-3 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中，客户端A写入x=1的时间是42.004秒，而客户端B写入x+=1即x=2虽然在后面发生但是时间是42.003秒。节点2在收到这两个事件时，会根据时间戳错误的认为x=1是最新的值，丢弃了x=2的值。&lt;/p&gt;&#xA;&lt;p&gt;这种冲突解决方式称为“最后写入获胜（Last Write Win）”，但是这样保持“最新”值并丢弃其他值的做法，由于“最新”的定义强依赖于墙上时钟，则会引入偏差。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;时钟的置信区间&#34;&gt;&#xA;  时钟的置信区间&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%97%b6%e9%92%9f%e7%9a%84%e7%bd%ae%e4%bf%a1%e5%8c%ba%e9%97%b4&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;不应该把墙上时间视为一个精确的时间点，而更应该被视为带有置信区间的时间范围。比如，系统有95%的置信度认为目前时间在[10.3,10.5]秒之间。&lt;/p&gt;&#xA;&lt;p&gt;比如Google Spanner中的TrueTime API，在查询当前时间时，会得到两个值：[不早于，不晚于]分别代表误差的最大偏差范围。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;进程暂停&#34;&gt;&#xA;  进程暂停&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%bf%9b%e7%a8%8b%e6%9a%82%e5%81%9c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;另外一个分布式系统中危险使用时钟的例子：假设数据库每个分区只有一个主节点，只有主节点可以接收写入，那么其它节点该如何确信该节点没有被宣告失效，可以继续安全写入呢？&lt;/p&gt;</description>
    </item>
    <item>
      <title>《数据密集型应用系统设计》第七章《事务》笔记</title>
      <link>https://www.codedump.info/zh/post/20190403-ddia-chapter07-transaction/</link>
      <pubDate>Wed, 03 Apr 2019 22:33:58 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190403-ddia-chapter07-transaction/</guid>
      <description>&lt;p&gt;事务提供了一种机制，应用程序可以把一组读和写操作放在一个逻辑单元里，所有在一个事务的读和写操作会被视为一个操作：要么全部失败，要么全部成功，因此应用程序不需要担心部分失败（partial failure）问题，可以安全的重试。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;深入理解事务&#34;&gt;&#xA;  深入理解事务&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3%e4%ba%8b%e5%8a%a1&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;事务提供的安全性保证即所谓的&lt;code&gt;ACID&lt;/code&gt;，它包括以下四个要求：&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;acid&#34;&gt;&#xA;  ACID&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#acid&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;原子性atomicity&#34;&gt;&#xA;  原子性（Atomicity）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%8e%9f%e5%ad%90%e6%80%a7atomicity&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;A（Atomicity，原子性）：在一个事务中的所有操作，要么全部成功，要么全部失败，不存在部分成功或者部分失败的情况。在出错时中断事务，前面成功的操作都会被丢弃。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;一致性consistency&#34;&gt;&#xA;  一致性（consistency）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%80%e8%87%b4%e6%80%a7consistency&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;C（Consistency，一致性）：对数据有特定的预期状态，任何数据修改必须满足这些状态约束，比如针对一个账号，账号上的款项必须保持平衡。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;隔离性isolation&#34;&gt;&#xA;  隔离性（isolation）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%9a%94%e7%a6%bb%e6%80%a7isolation&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I（Isolation，隔离性）：并发执行的多个事务，不会相互影响。&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;7-1&#34; src=&#34;https://www.codedump.info/media/imgs/20190403-ddia-chapter07-transaction/7-1.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 7-1 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&#xA;如上图中所示，两个客户端同时增加数据库的计时器，由于没有做好隔离，导致最终的结果是43而不是正确的44。&lt;/p&gt;&#xA;&lt;p&gt;ACID语义中的隔离性意味着并发执行的多个事务相互隔离，不能交叉运行。经典的数据库教材将隔离性定义为可串行化（serializability），这就意味着可以假装它是数据库上运行的唯一事务。&lt;/p&gt;&#xA;&lt;p&gt;然而实践中，由于性能问题很少使用串行化隔离。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;持久性durability&#34;&gt;&#xA;  持久性（Durability）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%8c%81%e4%b9%85%e6%80%a7durability&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;D（Durability，持久性）：一旦事务提交，数据将被持久化存储起来。&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%bc%b1%e9%9a%94%e7%a6%bb%e7%ba%a7%e5%88%ab&#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;h2 class=&#34;heading&#34; id=&#34;读提交read-committed&#34;&gt;&#xA;  读提交（read committed）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%af%bb%e6%8f%90%e4%ba%a4read-committed&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&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;/ol&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;防止脏读&#34;&gt;&#xA;  防止脏读&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%98%b2%e6%ad%a2%e8%84%8f%e8%af%bb&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&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;7-4&#34; src=&#34;https://www.codedump.info/media/imgs/20190403-ddia-chapter07-transaction/7-4.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 7-4 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如上图，用户2仅在用户1的事务提交成功之后，才能读取到这次事务修改的新值x=3。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;防止脏写&#34;&gt;&#xA;  防止脏写&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%98%b2%e6%ad%a2%e8%84%8f%e5%86%99&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&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;7-5&#34; src=&#34;https://www.codedump.info/media/imgs/20190403-ddia-chapter07-transaction/7-5.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 7-5 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如上图，alice和bob两人试图购买同一辆车。购买时需要两次数据库写入：网站需要更新买主为新买家，而同时发票也需要随之更新。&#xA;但是在上图中，车主被改成了bob，但是发票上面写的却是alice。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;实现读提交&#34;&gt;&#xA;  实现读提交&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%ae%9e%e7%8e%b0%e8%af%bb%e6%8f%90%e4%ba%a4&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;实现防脏写：数据库通常使用行级锁来防止脏写，事务想修改某个对象，必须首先获得该对象的锁，直到事务结束。&lt;/p&gt;&#xA;&lt;p&gt;实现防脏读：也可以使用前面的防脏写来实现防脏读，但是这样代价太大了。一般的方式是保存这个值的两个版本，事务没有提交之前返回旧的值，提交之后才返回新的值。&lt;/p&gt;&#xA;&lt;p&gt;然而，读锁在实际中并不可行，原因在于运行时间较长的事务导致了许多只读事务等待太长的时间。&lt;/p&gt;&#xA;&lt;p&gt;因此，大部分数据库使用7-4中的方式来防止脏读：对于每个待更新的对象，数据库都会维护其旧值和当前持有锁事务将要设置的新值两个版本。在事务提交之前返回的是旧值；仅当事务提交之后，才会切换到新的值。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;快照隔离级别snapshot-isolation和重复读&#34;&gt;&#xA;  快照隔离级别（Snapshot isolation）和重复读&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%bf%ab%e7%85%a7%e9%9a%94%e7%a6%bb%e7%ba%a7%e5%88%absnapshot-isolation%e5%92%8c%e9%87%8d%e5%a4%8d%e8%af%bb&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&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;7-6&#34; src=&#34;https://www.codedump.info/media/imgs/20190403-ddia-chapter07-transaction/7-6.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 7-6 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中，alice有两个账号，但是如果alice在转账过程中去查看账户，会发现少了100美元。&lt;/p&gt;&#xA;&lt;p&gt;原因在于：alice对两个账户的两次读操作是同一个事务，而在这两次读操作之间，还有两次写操作，在这两次写操作完成之后才进行的第二次读操作，这样读出来的数据就不一致了。&lt;/p&gt;&#xA;&lt;p&gt;这种异常现象称为”不可重复读取（nonrepeatable read）“或者”读倾斜（read skew）“问题。&lt;/p&gt;</description>
    </item>
    <item>
      <title>《数据密集型应用系统设计》第六章数据分区笔记</title>
      <link>https://www.codedump.info/zh/post/20181124-ddia-chapter06-partitioning/</link>
      <pubDate>Tue, 02 Apr 2019 22:17:24 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20181124-ddia-chapter06-partitioning/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;键值数据的分区&#34;&gt;&#xA;  键值数据的分区&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%94%ae%e5%80%bc%e6%95%b0%e6%8d%ae%e7%9a%84%e5%88%86%e5%8c%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;基于关键字区间的分区&#34;&gt;&#xA;  基于关键字区间的分区&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e4%ba%8e%e5%85%b3%e9%94%ae%e5%ad%97%e5%8c%ba%e9%97%b4%e7%9a%84%e5%88%86%e5%8c%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;给每个分区分配一段连续的关键字或者关键字区间（以最小值和最大值来指示），从关键字区间的上下限可以确定哪个分区包含这些关键字。&lt;/p&gt;&#xA;&lt;p&gt;关键字的区间段不一定要均匀分布，这是因为数据本身可能就不是均匀的。比如，某些分区包含以A和B开头字母的键，而某些分区包含了T、U、V、X、Y和Z开始的单词。&lt;/p&gt;&#xA;&lt;p&gt;基于关键字的区间分区的缺点是某些访问模式会导致热点（hot spot）。比如关键字是时间戳，分区对应一个时间范围，那么可能会出现所有的写入操作都集中在同一个分区（比如当天的分区），而其他分区始终处于空闲状态。&lt;/p&gt;&#xA;&lt;p&gt;为了避免类似的问题，需要使用时间戳以外的其他内容作为关键字的第一项。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;基于关键字hash值分区&#34;&gt;&#xA;  基于关键字Hash值分区&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e4%ba%8e%e5%85%b3%e9%94%ae%e5%ad%97hash%e5%80%bc%e5%88%86%e5%8c%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;基于关键字Hash值分区，可以解决上面提到的数据倾斜和热点问题，但是丧失了良好的区间查询特性。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;负载倾斜和热点&#34;&gt;&#xA;  负载倾斜和热点&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%b4%9f%e8%bd%bd%e5%80%be%e6%96%9c%e5%92%8c%e7%83%ad%e7%82%b9&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;基于关键字Hash值分区的办法，可以减轻数据热点问题，但是不能完全避免这类问题。一种常见的极端场景是，社交网络上某个名人有几百万的粉丝，当其发布一些热点事件时可能会引起访问风暴。此时，Hash起不到任何分流的作用。&lt;/p&gt;&#xA;&lt;p&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%88%86%e5%8c%ba%e4%b8%8e%e4%ba%8c%e7%ba%a7%e7%b4%a2%e5%bc%95&#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;基于文档分区的二级索引&#34;&gt;&#xA;  基于文档分区的二级索引&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e4%ba%8e%e6%96%87%e6%a1%a3%e5%88%86%e5%8c%ba%e7%9a%84%e4%ba%8c%e7%ba%a7%e7%b4%a2%e5%bc%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&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;figure 6-4&#34; src=&#34;https://www.codedump.info/media/imgs/20181124-ddia-chapter06-partitioning/figure6-4.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; figure 6-4 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中，数据根据ID 进行分区，但是实际查询的时候，还可以按照颜色和厂商进行过滤，所以每个分区上面还创建了颜色和厂商的索引。每次往分区中写入新数据时，自动创建这些二级索引。&lt;/p&gt;&#xA;&lt;p&gt;在这种索引方式中，每个分区完全独立。各自维护自己的二级索引。因此文档索引也成为本地索引，而不是全局索引。&lt;/p&gt;&#xA;&lt;p&gt;但是读取的时候，需要查询所有的分区数据然后进行合并才返回给客户端，这种叫分散/聚集（scatter/gather）。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;基于词条的二级索引&#34;&gt;&#xA;  基于词条的二级索引&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e4%ba%8e%e8%af%8d%e6%9d%a1%e7%9a%84%e4%ba%8c%e7%ba%a7%e7%b4%a2%e5%bc%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&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;figure 6-5&#34; src=&#34;https://www.codedump.info/media/imgs/20181124-ddia-chapter06-partitioning/figure6-5.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; figure 6-5 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中，所有数据分区中的颜色进行了分区，比如从a到r开始的颜色放在了分区0中，从s到z的颜色放在了分区1中，类似的，厂商索引也被分区。这种索引方式成为词条分区（term-partitioned）。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;优点：读取高效，不需要采用scatter/gather方式对所有分区都进行查询；&lt;/li&gt;&#xA;&lt;li&gt;缺点：写入速度慢并且非常复杂，主要是因为单个文档需要更新的时候，里面可能涉及多个二级索引，而二级索引又放在不同的节点上。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在实践中，对全局二级索引数据的更新一般都是异步进行的。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;分区再平衡rebalancing-partitions&#34;&gt;&#xA;  分区再平衡（Rebalancing Partitions）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%88%86%e5%8c%ba%e5%86%8d%e5%b9%b3%e8%a1%a1rebalancing-partitions&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;实际中，数据会发生某些变化，这时候需要将数据和请求从一个节点转移到另一个节点。这样的一个迁移负载的过程称为再平衡（rebalance）。&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;li&gt;避免不必要的负载迁移。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;下面谈各种再平衡策略。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;为什么不能用取模&#34;&gt;&#xA;  为什么不能用取模？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e8%83%bd%e7%94%a8%e5%8f%96%e6%a8%a1&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;对节点数进行取模的方式，最大的问题在于如果节点的数据发生了变化，会导致很多关键字从现有的节点迁移到另一个节点。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;固定数量的分区&#34;&gt;&#xA;  固定数量的分区&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9b%ba%e5%ae%9a%e6%95%b0%e9%87%8f%e7%9a%84%e5%88%86%e5%8c%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;创建远超实际节点数的分区数，然后给每个节点分配多个分区。比如只有10个节点的集群，划分了1000个逻辑分区。&lt;/p&gt;&#xA;&lt;p&gt;如果集群中添加了一个新节点，该新节点就可以从每个现有节点上匀走几个分区，直到分区再次达到全局平衡。&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;figure 6-6&#34; src=&#34;https://www.codedump.info/media/imgs/20181124-ddia-chapter06-partitioning/figure6-6.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; figure 6-6 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;动态分区&#34;&gt;&#xA;  动态分区&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%8a%a8%e6%80%81%e5%88%86%e5%8c%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;按节点比例分区&#34;&gt;&#xA;  按节点比例分区&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%8c%89%e8%8a%82%e7%82%b9%e6%af%94%e4%be%8b%e5%88%86%e5%8c%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;自动与手动再平衡操作&#34;&gt;&#xA;  自动与手动再平衡操作&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%87%aa%e5%8a%a8%e4%b8%8e%e6%89%8b%e5%8a%a8%e5%86%8d%e5%b9%b3%e8%a1%a1%e6%93%8d%e4%bd%9c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;请求路由&#34;&gt;&#xA;  请求路由&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%af%b7%e6%b1%82%e8%b7%af%e7%94%b1&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;当客户端需要发起请求时，如果知道应该连接哪个节点？如果发生了分区再平衡，分区与节点的对应关系发生了变化。&lt;/p&gt;</description>
    </item>
    <item>
      <title>《数据密集型应用系统设计》第五章数据复制笔记</title>
      <link>https://www.codedump.info/zh/post/20181118-ddia-chapter05-replication/</link>
      <pubDate>Mon, 01 Apr 2019 18:19:22 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20181118-ddia-chapter05-replication/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;主从复制&#34;&gt;&#xA;  主从复制&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%bb%e4%bb%8e%e5%a4%8d%e5%88%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&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;figure 5-1&#34; src=&#34;https://www.codedump.info/media/imgs/20181118-ddia-chapter05-replication/figure5-1.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; figure 5-1 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;同步复制和异步复制&#34;&gt;&#xA;  同步复制和异步复制&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%90%8c%e6%ad%a5%e5%a4%8d%e5%88%b6%e5%92%8c%e5%bc%82%e6%ad%a5%e5%a4%8d%e5%88%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;同步复制&#34;&gt;&#xA;  同步复制&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%90%8c%e6%ad%a5%e5%a4%8d%e5%88%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&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;h3 class=&#34;heading&#34; id=&#34;异步复制&#34;&gt;&#xA;  异步复制&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%bc%82%e6%ad%a5%e5%a4%8d%e5%88%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&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;不能把集群中所有节点设置为同步节点，因为这样的话任何一个节点的停滞都会导致整个集群的不可用。像Paxos、Raft算法，都要求集群中大多数节点返回就可以了。部分同步、部分异步的集群配置成为半同步（semi-sync）的集群配置。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;新增新的从节点&#34;&gt;&#xA;  新增新的从节点&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%96%b0%e5%a2%9e%e6%96%b0%e7%9a%84%e4%bb%8e%e8%8a%82%e7%82%b9&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&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;li&gt;重复上面三步直到从节点追上主节点的进度。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;处理节点失效&#34;&gt;&#xA;  处理节点失效&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%a4%84%e7%90%86%e8%8a%82%e7%82%b9%e5%a4%b1%e6%95%88&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;从节点失效&#34;&gt;&#xA;  从节点失效&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bb%8e%e8%8a%82%e7%82%b9%e5%a4%b1%e6%95%88&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;从节点崩溃恢复之后按照前面新增新的从节点的步骤来追上主节点的数据进度。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;主节点失效&#34;&gt;&#xA;  主节点失效&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%bb%e8%8a%82%e7%82%b9%e5%a4%b1%e6%95%88&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&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;除了以上步骤之外，还有以下问题需要考虑：&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;可能会出现集群同时存在两个主节点的情况，也就是所谓的脑裂（split brain）现象，此时两个主节点都认为自己是主节点并且都能接收客户端的写数据请求，会导致数据丢失或者破坏。&lt;/li&gt;&#xA;&lt;li&gt;如何设置合理的超时时间来判断主节点失效？如果太大意味着总体恢复时间长，如果太小意味着某些情况下可能主节点并未失效但是被误判为失效了，比如网络峰值导致延迟高等原因，这样会导致很多不必要的主节点切换。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;上述的问题，包括节点失效、网络不可靠、副本一致性、持久性、可用性与延迟之间的各种细微的权衡，正是分布式系统核心的基本问题。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;复制日志的实现&#34;&gt;&#xA;  复制日志的实现&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%a4%8d%e5%88%b6%e6%97%a5%e5%bf%97%e7%9a%84%e5%ae%9e%e7%8e%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;基于语句的复制&#34;&gt;&#xA;  基于语句的复制&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e4%ba%8e%e8%af%ad%e5%8f%a5%e7%9a%84%e5%a4%8d%e5%88%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;主节点记录所执行的每个写请求并将该语句做为日志发送给从节点。但是有些场景并不适合这么做，比如：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;调用任何非确定函数的语句，比如NOW()获得当前时间，RAND()返回一个随机数。&lt;/li&gt;&#xA;&lt;li&gt;语句中使用了自增列，或者依赖于当前数据库的数据。&lt;/li&gt;&#xA;&lt;li&gt;有副作用的语句，在每个副本上面执行的效果不一样。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;基于预写日志wal&#34;&gt;&#xA;  基于预写日志(WAL)&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e4%ba%8e%e9%a2%84%e5%86%99%e6%97%a5%e5%bf%97wal&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;将对数据库的操作写入日志，传送到从节点上然后执行，得到与主节点相同的数据副本。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;基于行的逻辑日志复制&#34;&gt;&#xA;  基于行的逻辑日志复制&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e4%ba%8e%e8%a1%8c%e7%9a%84%e9%80%bb%e8%be%91%e6%97%a5%e5%bf%97%e5%a4%8d%e5%88%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;所谓的逻辑日志，就是复制与存储引擎采用不同的日志格式，这样复制与存储逻辑剥离，这种日志称为逻辑日志，与物理存储引擎的数据区分开。由于逻辑日志与存储引擎逻辑上解耦，因此可以更好的向后兼容，也更好的能被外部程序解析。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
