<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Paper Reading on NoneBack</title><link>https://noneback.github.io/zh/tags/paper-reading/</link><description>Recent content in Paper Reading on NoneBack created by</description><generator>Hugo -- gohugo.io</generator><language>zh</language><copyright>@NoneBack All rights reserved</copyright><lastBuildDate>Mon, 19 Feb 2024 11:12:16 +0800</lastBuildDate><atom:link href="https://noneback.github.io/zh/tags/paper-reading/index.xml" rel="self" type="application/rss+xml"/><item><title>Borg: Large-scale cluster management at Google with Borg</title><link>https://noneback.github.io/zh/blog/zh/borg/</link><pubDate>Mon, 19 Feb 2024 11:12:16 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/borg/</guid><description>&lt;p&gt;Borg 是一个集群管理系统，类似闭源的k8s。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过准入控制、高效的任务装箱、超售、机器共享、以及进程级别的性能隔离，实现了高利用率。&lt;/li&gt;
&lt;li&gt;为高可用应用提供了可以减少故障恢复时间的运行时特性，以及降低关联故障概率的调度策略。&lt;/li&gt;
&lt;li&gt;提供声明式的作业描述语言、域名服务集成、实时作业监控、分析和模拟系统行为的工具。简化用户的使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文章本意在介绍系统设计的同时，讲述Google在背后的经验总结。但本文主要关心系统的设计，关注Borg对外提供的服务SLA，关注Borg对负载，资源，调度等方面的抽象。&lt;/p&gt;
&lt;h2 id="系统抽象"&gt;系统抽象&lt;/h2&gt;
&lt;p&gt;工作负载上，主要有两种：一是长期运行的服务，二是批处理任务。分别对应两种类型的Job (prod / non-prod)。Job由若干Task组成，不同的Job拥有不同的优先级。&lt;/p&gt;
&lt;p&gt;部署架构上，一个Borg Cluster由若干Cell组成，一个Cell由若干机器组成。&lt;/p&gt;
&lt;p&gt;任务调度上，一切机器上有限的物理或者逻辑单元都需要被视为资源，如cpu，mem，io等资源。&lt;/p&gt;
&lt;h2 id="系统架构"&gt;系统架构&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/202401291404127.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/202401291404127.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;Master-Slave架构，由一个BorgMaster和若干Borglet节点组成，调度器是一个独立的服务。&lt;/p&gt;
&lt;p&gt;BorgMaster是一个逻辑节点，负责与外部以及Borglet的交互，以及集群内状态的维护。利用Paxos实现多副本以及ha容错。&lt;/p&gt;
&lt;p&gt;Borglet是Cell内没台机器的Borg Proxy。负责启停Task，管理Node的物理资源，上报状态。&lt;/p&gt;
&lt;p&gt;Scheduler是负责进行Task调度的服务。使用Master中记录的状态，并基于此异步的去处理Task的调度，并告知Master二次判断。&lt;/p&gt;
&lt;h2 id="资源调度"&gt;资源调度&lt;/h2&gt;
&lt;p&gt;Scheduler是其中的很重要的核心服务。调度算法的好坏决定了资源的利用率，直接与使用成本关联。&lt;/p&gt;
&lt;h3 id="基本流程"&gt;基本流程&lt;/h3&gt;
&lt;p&gt;调度算法有两个部分:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可行性检查&lt;/strong&gt;，找到一组可以运行任务的机器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;评分&lt;/strong&gt;，从中选择一个合适的机器。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在可行性检查阶段，调度器会找到一组满足任务约束且有足够可用资源的机器，其中可用资源包括已经 分配给低优先级任务但可以抢占的资源。&lt;/p&gt;
&lt;p&gt;在评分阶段，调度器确定每台可行机器的适宜性。评分考虑了用户特定的偏好，但主要取决于内建的标准：例如最小化被抢占任务的个数和优先级，选择已经有该任务安 装包的机器，尽可能使任务分散在不同的供电和故障域，以及装箱(Packing)质量(在单台机器上混合 高、低优先级的任务，以允许高优先级任务在负载尖峰扩容)等。&lt;/p&gt;
&lt;p&gt;调度器使用一份缓存的 Cell 状态拷贝，重复执行下面的操作:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从选举出来的 Master 获取状态变更(包括已分配的和等待中的工作);更新自己的本地拷贝。&lt;/li&gt;
&lt;li&gt;执行一轮调度来分配任务;将分配信息发送给 Master。&lt;/li&gt;
&lt;li&gt;Master 会接受并应用这些分配，但如果分配不适合(例如，是基于过时的状态做出的)，就会等待调度器的下一轮调度。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="其他"&gt;其他&lt;/h3&gt;
&lt;p&gt;文中还提到了如何提供售卖率以及性能争抢的问题，不过不是本文重点，可以参考一下原文。&lt;/p&gt;
&lt;h2 id="reference"&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/hellojamest/p/16526159.html"&gt;https://www.cnblogs.com/hellojamest/p/16526159.html&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Percolator: Large-scale Incremental Processing Using Distributed Transactions and Notifications</title><link>https://noneback.github.io/zh/blog/zh/percolator/</link><pubDate>Thu, 28 Sep 2023 10:43:23 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/percolator/</guid><description>&lt;p&gt;好久没学习了，学点想学的。本次内容是分布式事务Percolator, 不翻译论文，没有算法细节，只记录自己的理解。&lt;/p&gt;
&lt;h2 id="percolator-和-2pc"&gt;Percolator 和 2PC&lt;/h2&gt;
&lt;h3 id="2pc"&gt;2PC&lt;/h3&gt;
&lt;p&gt;两阶段提交协议中包含两种角色，&lt;strong&gt;Coordinator和Participant&lt;/strong&gt;。协调者负责整个协议的推进，使得多个参与者最终达到一致的决议。参与者响应协调者的请求，根据协调者的请求完成 prepare 操作及 commit/abort 操作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2PC协议保证事务的原子性(ACD)&lt;/strong&gt;，并&lt;strong&gt;未对隔离性(I)做任何实现&lt;/strong&gt;，依赖单机事务(ACD)。Coordinator，显然是关键路径，可能成为单点瓶颈，或者宕机问题阻塞事务流程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="code-block" data-language="text"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;text&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; 协调者 参与者
QUERY TO COMMIT
--------------------------------&amp;gt;
VOTE YES/NO prepare*/abort*
&amp;lt;-------------------------------
commit*/abort* COMMIT/ROLLBACK
--------------------------------&amp;gt;
ACKNOWLEDGMENT commit*/abort*
&amp;lt;--------------------------------
end&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="percolator"&gt;Percolator&lt;/h3&gt;
&lt;p&gt;本身其实算一种经过优化的二阶段提交的实现，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对锁的使用进行优化，引入Primary-Secondary二级锁，去除了对&lt;strong&gt;Coordinator&lt;/strong&gt;的依赖。&lt;/li&gt;
&lt;li&gt;提供了完整的ACID事务语义实现，并且支持MVCC（依赖时间戳服务）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="percolator协议细节"&gt;Percolator协议细节&lt;/h2&gt;
&lt;p&gt;Percolator系统主要有三部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Client，发起事务的客户端，Client 是整个协议的控制中心，是两阶段提交的协调者（Coordinator）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TO，Time Observer，分配时间戳，提供全局唯一且递增的时间戳，实现MVCC&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bigtable，提供单机单行事务，数据存储在Bigtable中，包含数据本身以及附带的一些属性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;lock + write + data&lt;/code&gt;: for transaction，lock表示cell被事务持有，write表示数据的可见性&lt;/p&gt;
&lt;p&gt;&lt;code&gt;notify + ack&lt;/code&gt;: for watcher or notifier&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/20230927163910.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/20230927163910.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从外部看，Percolator以SDK的提供给业务方使用。提供事务以及R/W。模型类似于 &lt;code&gt;Begin Txn → Sets of RW Operations → Commit or About or Rollback&lt;/code&gt;其中Bigtable最为持久化组件屏蔽了底层Tablet Server Data Sharding的细节，事务中的每一个写操作或者读后写（统称为Write）操作都视为分布式事务的参与者，而这些操作最后可能会分派到多个Tablet Server节点上。&lt;/p&gt;
&lt;h3 id="算法流程"&gt;算法流程&lt;/h3&gt;
&lt;p&gt;一个事务的所有 Write 在提交之前都会先缓存在 Client，然后在提交阶段一次性写入；Percolator 的事务提交是标准的两阶段提交，分为 Prewrite 和 Commit 。&lt;/p&gt;
&lt;h4 id="prewrite"&gt;Prewrite&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;从TO获取一个时间戳，作为事务的开始时间。&lt;/li&gt;
&lt;li&gt;给数据上锁，标记数据被当前事务占有。上锁失败则表示数据被其他事物占有，当前事物失败。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;上锁过程利用了Primary-Secondary机制，选择一个 Write 作为 primary，其它 Write 则是 secondary。Secondary的锁记录指向Primary。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/202309271613141.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/202309271613141.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;显然，Prewrite阶段的数据对其他事务均不可见。&lt;/p&gt;
&lt;h4 id="commit"&gt;Commit&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;尝试对Prewrite中的数据进行Commit。Commit的时候先对Primary记录进行Commit，Primary记录的提交时间将作为整个事务的提交时间。首先对记录的锁记录进行检测，如果锁不存在，则表示Prewrite阶段的锁被其他事物清理，则事物执行失败。如果存在，则写记录中的write列，表示数据对系统可见。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;异步网络之中，单节点故障，网络延迟很常见。算法需要在发现这些故障的时候，清理掉这些锁记录，避免死锁。所以，在Commit阶段，如果锁不存在，则表示事务的参与者发生了问题当前事务需要被清理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;提交成功之后对锁记录进行清理。显然，锁清理也是可以异步的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些设计使得算法去除了中心化&lt;strong&gt;Coordinator&lt;/strong&gt;的&lt;strong&gt;&lt;strong&gt;依赖&lt;/strong&gt;&lt;/strong&gt;。因为过去需要依赖这个中心服务来维护事务各个参与者的信息。而在本算法中，利用Primary-Secondary二级锁以及Write列就可以实现。Write列表示数据对外的可见性以及数据版本链条。Lock列表示数据被事务持有。Primary-Secondary记录了参与者的逻辑从属关系。这样的设计使得Primary记录的提交变成了整个事务的提交点。一但Primary被提交，所有的Secondary记录可以通过检查对应Primary记录的Write列来进行异步提交。&lt;/p&gt;
&lt;h3 id="snapshot-isolation"&gt;Snapshot Isolation&lt;/h3&gt;
&lt;p&gt;两阶段提交解决的是事务的原子性。在此基础上，Percolator还提供了&lt;strong&gt;&lt;strong&gt;Snapshot Isolation&lt;/strong&gt;&lt;/strong&gt;的隔离性。简而言之，Snapshot Isolation要求提交的事务不能导致数据冲突，事务的读操作满足Snapshot Read。利用事务开始时间以及Primary记录的提交时间，可以维护一个事务之间的全序关系，这些问题自然就可以解决了。&lt;/p&gt;
&lt;h3 id="异步网络的死锁问题"&gt;异步网络的死锁问题&lt;/h3&gt;
&lt;p&gt;之前提到，异步网络之中，单节点故障，网络延迟很常见。算法需要在发现这些故障的时候，清理掉这些锁记录，避免死锁。故障检测的策略可以很简单，比如超时，故障会导致当前事务失败；节点故障又恢复正常，当时的事务已经失败，则此时需要对节点上相关锁记录进行清理。锁的清理可以异步化，比如在Prewrite上锁时，当发现记录Lock列非空，则去检测其Primary锁是否为空，Primary非空，则表示这个事务为完成，可以清理；为空则表示事务提交，则去找到事务提交时间，并把数据提交，再清理锁记录（RollForward）。&lt;/p&gt;
&lt;h3 id="通知机制"&gt;通知机制&lt;/h3&gt;
&lt;p&gt;通知机制对于异步系统的状态观测和联动很重要。但不是本文重点。&lt;/p&gt;
&lt;h2 id="percolator在tidb上的使用"&gt;Percolator在TiDB上的使用&lt;/h2&gt;
&lt;p&gt;基于我们上文的分析，我们可以得出这样的结论：Percolator是一种优化过的2PC分布式事务实现，实现的基础是支持单机事务的存储引擎。&lt;/p&gt;
&lt;p&gt;让我们来简单看看TiDB的的基于Percolator算法实现分布式事务。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://download.pingcap.com/images/docs-cn/tidb-architecture-v6.png" alt="https://download.pingcap.com/images/docs-cn/tidb-architecture-v6.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;TiDB和TiKV架构如上。TiDB关系表里的数据最终都被映射到了TiKV中的KV中。TiKV是一个基于Raft+RocksDB的分布式KV。RocksDB是一个支持事务操作的KV。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://download.pingcap.com/images/docs/tikv-rocksdb.png" alt="https://download.pingcap.com/images/docs/tikv-rocksdb.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;所以TiDB的事务的执行路径可以是这样：对关系表的事务操作转为对一组KV的事务操作，再基于Percolator去执行，以此实现关系表的事务操作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当然不可能提供和单机TP数据库一样的事务语义和性能保证。但Share Nothing架构也有自己的优点，所以这个也许并不重要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://zhuanlan.zhihu.com/p/22594180"&gt;两阶段提交的工程实践&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://mysql.taobao.org/monthly/2018/11/02/"&gt;PolarDB 数据库内核月报&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://karellincoln.github.io/2018/04/05/percolator-translate/"&gt;percolator：在线增量处理系统 中文翻译&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.notion.so/percolator-879c8f72f80b4966a2ec1e41edc74560?pvs=21"&gt;percolator：在线增量处理系统 中文翻译 | 一只小小鸟&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://zh.wikipedia.org/zh-hans/%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4"&gt;二阶段提交 - 维基百科，自由的百科全书&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cn.pingcap.com/blog/percolator-and-txn"&gt;Percolator 和 TiDB 事务算法&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.oceanbase.wiki/concept/transaction-management/transactions/distributed-transactions/two-phase-commit"&gt;两阶段提交 | OceanBase 学习指南&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.pingcap.com/zh/tidb/stable/tidb-architecture"&gt;TiDB 整体架构&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-Raft</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-raft/</link><pubDate>Mon, 21 Feb 2022 01:26:46 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-raft/</guid><description>&lt;p&gt;这个寒假可算把搁置许久的Lab02给做完了。之前一直被卡在Test 2B的一个case里，寒假时候重新看看大佬们的实现思路，可算是完成了所有内容，于是简单记录一下。&lt;/p&gt;
&lt;h2 id="算法简介"&gt;算法简介&lt;/h2&gt;
&lt;p&gt;共识算法的基础是复制状态机，即&lt;strong&gt;按照相同顺序执行相同的确定性指令最终必然达到一致状态&lt;/strong&gt;。Raft是一种代替Paxos的分布式共识算法，相比Paxos更利于学习与理解。&lt;/p&gt;
&lt;p&gt;Raft算法核心内容可以分为三个部分： $Leader Election + Log Replication + Satety$ 。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/19/9mGfndCtDHzMqe4.png" alt="img" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;集群机器初始为Follower，一旦一定时间内未接收到来自Leader的心跳，机器将成为Candidate并触发选举，请求剩下Follower投票。获得半数以上选票的Candidate成为Leader。&lt;/p&gt;
&lt;p&gt;Raft是一种&lt;strong&gt;强领导人&lt;/strong&gt;的强一致性的分布式共识算法，它使用Term作为逻辑时钟，一个任期中只能有领导人。领导人需要周期性发送心跳以维护其地位，同时需要处理&lt;strong&gt;复制提交&lt;/strong&gt;日志。&lt;/p&gt;
&lt;p&gt;复制日志时，Leader首先将日志复制到其他Follower上，直到半数以上的Follower成功复制，Leader才会提交此日志。&lt;/p&gt;
&lt;p&gt;安全性主要有五个部分，与实现相关的最核心的内容我认为有两个。一个是领导人只追加原则，不允许修改已提交的日志；另一个是选举安全性，避免脑裂问题，同时保证新Leader拥有比较新的日志。&lt;/p&gt;
&lt;p&gt;剩下的其他内容请参考论文原文。&lt;/p&gt;
&lt;h2 id="实现思路"&gt;实现思路&lt;/h2&gt;
&lt;p&gt;实现的思路大体上是参考了一篇大佬的博文（见参考），算法的细节很多也在原Paper的Figure2中，故而以下只讲一下实现各个功能时需要注意的地方。&lt;/p&gt;
&lt;h3 id="领导人选举"&gt;领导人选举&lt;/h3&gt;
&lt;h4 id="发起选举选举结果处理"&gt;发起选举+选举结果处理&lt;/h4&gt;
&lt;p&gt;发起选举是会开启多个goroutine后台发送RPC请求到其他结点，所以处理RPC response的时候需要确定当前结点为Candidate，以及请求未过期，即&lt;code&gt;rf.state == Candidate &amp;amp;&amp;amp; req.Term == rf.currentTerm&lt;/code&gt;。选举成功需要立即发送心跳，通知其他结点选举结果。&lt;/p&gt;
&lt;p&gt;如果发现失败的Response&lt;code&gt;resp.Term &amp;gt; rf.currentTerm&lt;/code&gt;,此时需要切换到Follower状态，更新任期，并&lt;strong&gt;重置投票信息&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上一旦更新了任期，就需要重置投票信息。如果不重置votedFor信息，会有一些测试通过不了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="请求投票rpc"&gt;请求投票RPC&lt;/h4&gt;
&lt;p&gt;前置逻辑过滤过期&lt;code&gt;req.Term &amp;lt; rf.currentTerm&lt;/code&gt;以及当前任期的重复投票请求。之后再按照算法描述的逻辑处理，注意如果成功投票，需要重置选举计时器。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 grant 投票时才重置选举超时时间，这样有助于网络不稳定条件下选主的 liveness 问题&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="状态切换"&gt;状态切换&lt;/h4&gt;
&lt;p&gt;注意在切换角色时处理不同的计时器状态(stop or reset)，切换到Leader时需要重置matchIndex以及nextIndex的值。&lt;/p&gt;
&lt;h3 id="日志复制"&gt;日志复制&lt;/h3&gt;
&lt;p&gt;Raft算法的核心，需要注意的地方最多。&lt;/p&gt;
&lt;p&gt;我的实现是使用多个replicator和applier线程异步复制和apply的方式。&lt;/p&gt;
&lt;h4 id="日志复制rpc"&gt;日志复制RPC&lt;/h4&gt;
&lt;p&gt;前置逻辑过滤掉&lt;code&gt;req.Term &amp;lt; rf.currentTerm&lt;/code&gt;过期的请求。之后处理日志不一致以及日志被压缩以及重复日志的情况，之后复制日志再处理&lt;code&gt;commitIndex&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id="发起日志复制请求结果处理"&gt;发起日志复制+请求结果处理&lt;/h4&gt;
&lt;p&gt;发起日志复制需要判断是直接复制日志或者发送快照。&lt;/p&gt;
&lt;p&gt;请求结果处理重点是如何处理&lt;code&gt;matchIndex&lt;/code&gt;和&lt;code&gt;nextIndex&lt;/code&gt;以及&lt;code&gt;commitIndex&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;matchIndex&lt;/code&gt;用来记录其他节点成功复制的最新日志，&lt;code&gt;nextIndex&lt;/code&gt;是记录发送给其他节点的下一个日志。&lt;code&gt;commitIndex&lt;/code&gt;通过排序&lt;code&gt;matchIndex&lt;/code&gt;来更新。再决定是否需要触发applier更新&lt;code&gt;appliedIndex&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;请求失败则可以回退nextIndex或者切换到Follower状态。&lt;/p&gt;
&lt;h4 id="异步apply"&gt;异步Apply&lt;/h4&gt;
&lt;p&gt;实际上就是一个后台goroutine，通过条件变量控制，使用Channel通信。每次触发会把&lt;code&gt;log[lastApplied:commitIndex]&lt;/code&gt;发送给上层，并更新&lt;code&gt;appliedIndex&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="持久化"&gt;持久化&lt;/h3&gt;
&lt;p&gt;在需要持久化状态的属性更新时及时的刷盘。&lt;/p&gt;
&lt;h3 id="安装快照"&gt;安装快照&lt;/h3&gt;
&lt;p&gt;主要就是Leader触发的Snapshot以及RPC。应用Snapshot的时候需要先判断其新旧以及更新&lt;code&gt;log[0]&lt;/code&gt;和&lt;code&gt;appliedIndex&lt;/code&gt;以及&lt;code&gt;commitIndex&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="坑"&gt;坑&lt;/h2&gt;
&lt;h3 id="defer"&gt;Defer&lt;/h3&gt;
&lt;p&gt;首先是Golang的&lt;strong&gt;defer&lt;/strong&gt;关键字。我比较喜欢在RPC开头使用defer关键字直接打印出结点的一些数据：&lt;code&gt;defer Dprintf(&amp;quot;%+v&amp;quot;, raft.currentTerm)&lt;/code&gt;，这样在调用结束时能打印出log，但实际上，在运行到defer这一行的代码时，打印的内容已经固定。正确的使用方式应该是&lt;code&gt;defer func(ID int) { Dprintf(&amp;quot;%+v&amp;quot;, id) }()&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="log-dummy-header"&gt;Log Dummy Header&lt;/h3&gt;
&lt;p&gt;Log处最好预留一个位置用于存放快照保存的Index和Term，不然Snapshot那部分的重构很痛苦。&lt;/p&gt;
&lt;h3 id="lock"&gt;Lock&lt;/h3&gt;
&lt;p&gt;参看guidance的用锁建议。使用一个大锁，而不是用多个锁。算法的正确性比性能重要。在发送RPC以及使用Channel时不要加锁，不然可能会超时。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://zh.wikipedia.org/wiki/Raft"&gt;Raft wikepedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://raft.github.io/"&gt;Raft Official Website&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf"&gt;Raft Paper&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pdos.csail.mit.edu/6.824/index.html"&gt;MIT6.824 Official&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/OneSizeFitsQuorum/MIT6.824-2021/blob/master/docs/lab2.md"&gt;Potato’s Implimentation Doc&lt;/a&gt;&lt;/p&gt;</description></item><item><title>DFS-Haystack</title><link>https://noneback.github.io/zh/blog/zh/dfs-haystack/</link><pubDate>Wed, 06 Oct 2021 22:44:01 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/dfs-haystack/</guid><description>&lt;p&gt;组内的主要项目便是一种提供POXIS文件系统语义的DFS，其中解决losf(lots of small files)的思路就是对小文件单独处理。里面的思想来源应该就是Haystack。
于是大致阅读了一下这篇论文，写下学习笔记。
笔记依旧不深究具体细节，仅仅记录对问题的思考以及设计的思路。&lt;/p&gt;
&lt;h2 id="介绍"&gt;介绍&lt;/h2&gt;
&lt;p&gt;HayStack是Facebook为了小文件设计的一种存储系统。之前的DFS，对于文件的寻址路径一般是都会使用Cache来缓存元数据，以便减少磁盘交互提高寻址效率。一个文件就需要一个维护一类元数据，文件数决定了元数据量。在高并发场景下，我们要减少磁盘IO，一般会选择将寻址需要的元数据缓存在内存中。&lt;/p&gt;
&lt;p&gt;在大量小文件场景下，会有大量的元数据。考虑到在内存元数据维护带来的开销，这种方案变得不可用。于是便有了为小文件特别优化的HayStack。它核心思想是将多个小文件聚合成一个大文件，以减少元数据。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;论文中的小文件其实是特指图片数据。&lt;/p&gt;
&lt;p&gt;Facebook是以社交起家的公司。在社交场景中，图片的上传和读取是常见需求。当业务发展到一定的程度，就需要有专门的服务支撑图片的大量高并发读写请求。&lt;/p&gt;
&lt;p&gt;在社交场景下，这类数据的特点是&lt;code&gt;written once, read often, never modified, and rarely deleted.&lt;/code&gt;基于此，Facebook开发了HayStack来支持图片分享服务。&lt;/p&gt;
&lt;h2 id="设计"&gt;设计&lt;/h2&gt;
&lt;h3 id="传统的设计"&gt;传统的设计&lt;/h3&gt;
&lt;p&gt;论文中给出了两种历史设计：基于CDN和基于NAS的方案&lt;/p&gt;
&lt;h4 id="基于cdn的方案"&gt;基于CDN的方案&lt;/h4&gt;
&lt;p&gt;这个方案的核心其实就是利用CDN对热点图片数据进行缓存，减少网络传输。&lt;/p&gt;
&lt;p&gt;常用的设计，对于热点图片的优化很显著。但问题也很明显，一是CDN价格昂贵容量有限；二是图片分享场景，也会有很多&lt;code&gt;less popular&lt;/code&gt;的图片数据，这就会导致长尾效应，拖慢速度。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guuaefh22gj610g0s4gnh02.jpg" alt="image-20210926200920113" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CDN其实一般服务于静态数据的，并且一般都是在活动之前进行预热，并不适合作为一种图片缓存服务使用。很多的&lt;code&gt;less popular&lt;/code&gt;的数据其实并未进入CDN，故而导致长尾效应。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="基于nas的方案"&gt;基于NAS的方案&lt;/h4&gt;
&lt;p&gt;这是facebook最初的设计方案，本质和基于CDN的方案区别不大，属于它的一种实现。&lt;/p&gt;
&lt;p&gt;引入NAS横向拓展存储，引入文件系统语义，同时也会有磁盘IO存在。和本地文件类似，对于未读取过的数据，至少需要三次磁盘IO：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read directory metadata into memory&lt;/li&gt;
&lt;li&gt;Load the inode into memory&lt;/li&gt;
&lt;li&gt;Read the content of the file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PhotoStore作为一层缓存使用，缓存一些元数据如file handle等，以加速寻址过程。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guuafar1rpj60u80scmyx02.jpg" alt="image-20210926201012724" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;基于NAS的设计并没有解决&lt;strong&gt;元数据过多导致不适合全量缓存&lt;/strong&gt;这个关键问题，一但文件数量到达临界值，不可避免的需要进行磁盘IO。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更本质的问题其实是&lt;strong&gt;文件与寻址元数据一一对应的关系&lt;/strong&gt;，使得元数据量随着文件数的变化而变化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以，优化的关键是，改变&lt;strong&gt;文件与寻址元数据一一对应的关系&lt;/strong&gt;，降低寻址过程中磁盘IO出现的频率。&lt;/p&gt;
&lt;h3 id="基于haystack的方案"&gt;基于HayStack的方案&lt;/h3&gt;
&lt;p&gt;此方案最核心的思路是&lt;strong&gt;将多个小文件聚合成大文件&lt;/strong&gt;，并只维护一份元数据。本质上是改变了元数据与文件数的映射关系，使将所有元数据保存在内存的方案成为可能。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;只维护聚合后的大文件的元数据，小文件在大文件中的位置需要另外维护映射&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNgy1guv5ytgsmtj60k20jqjsk02.jpg" alt="image-20210927142131959" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;h2 id="实现"&gt;实现&lt;/h2&gt;
&lt;p&gt;Haystack主要有三个组件，Haystack Directory、Haystack Cache、Haystack Store&lt;/p&gt;
&lt;h3 id="文件映射与数据落盘"&gt;文件映射与数据落盘&lt;/h3&gt;
&lt;p&gt;文件数据最终保存在logic volume上，一个logic volume对应多机器上的多个physical volume。&lt;/p&gt;
&lt;p&gt;用户首先访问Directory来获取访问路径，之后再通过Directory生成的URL访问其他组件，获取需要的数据。&lt;/p&gt;
&lt;h3 id="组件"&gt;组件&lt;/h3&gt;
&lt;h4 id="haystack-directory"&gt;Haystack Directory&lt;/h4&gt;
&lt;p&gt;属于Haystack的接入层，主要负责&lt;strong&gt;文件寻址&lt;/strong&gt;以及&lt;strong&gt;访问控制&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;读写请求首先访问Directory。对于读请求，Directory会生成一个访问URL，URL包含了访问的路径：&lt;code&gt;http://{cdn}/{cache}/{machine id}/{logicalvolume,Photo}&lt;/code&gt;。对于写请求，它会指定一个可以写入的volume提供用户写入。&lt;/p&gt;
&lt;p&gt;详细点来说，最主要有四个功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;负载均衡，平衡读写请求，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;决定请求的访问路径，比如是否通过CDN访问，生产访问URL&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;元数据与映射管理， 如&lt;code&gt;logic attr and logic volume =&amp;gt; list of phycial volume mapping&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;logic volume读写管理，logic volume可能是Readonly或者WriteEnabled&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这部分设计和数据特点有关，write once and read more。可以提高并发度。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于此，Directory就必须记录一些必须的元数据，file to volume，logical to phycial mapping、logical volume attr（size，owner、etc）。&lt;/p&gt;
&lt;p&gt;依赖分布式KV落盘元数据和缓存服务加速访问，以此保证容错可用以及低延迟。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Proxy，Metadata Mapping，Access Control&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="haystack-cache"&gt;Haystack Cache&lt;/h4&gt;
&lt;p&gt;Cache作为缓存用于优化寻址以及图像获取。核心的设计是&lt;strong&gt;Cache Rule&lt;/strong&gt;，判断什么样的数据需要被缓存以及&lt;strong&gt;Cache Miss&lt;/strong&gt;的处理。&lt;/p&gt;
&lt;p&gt;Haystack中，被缓存的图片数据需要满足这两个特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The request comes directly from a user and not the CDN&lt;/li&gt;
&lt;li&gt;The photo is fetched from a write-enabled Store machine&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当出现Cache Miss时，它会从Store上获取图片数据并同步推送至浏览器以及CDN中。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这样的策略是基于图片请求的场景与特点决定的。&lt;/p&gt;
&lt;p&gt;首先，在CDN中Miss的请求，很大概率上也会在Cache中miss，所以重CDN重定向的请求的数据不会被Cache。其次，图片往往在刚刚写入后不久时间内会被频繁的访问，所以这样的数据理应被Cache。&lt;/p&gt;
&lt;p&gt;CDN可以被视为一个External Cache。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="haystack-store"&gt;Haystack Store&lt;/h4&gt;
&lt;p&gt;Store属于Haystack的存储层，负责数据存储相关操作。&lt;/p&gt;
&lt;p&gt;首先是它的寻址抽象：&lt;code&gt;filename + offset =&amp;gt; logical volume id + offset =&amp;gt; data&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;多个Physical Volume组成Logical Volume，并作为实际落盘单位进行维护。在Store中，小文件被抽象成&lt;strong&gt;Needle&lt;/strong&gt;，交由Physical Volume进行管理和维护。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gv5oo0mltfj60zs0u0q5j02.jpg" alt="image-20211006164409801" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Needle实际上是对小文件的一种&lt;strong&gt;封装&lt;/strong&gt;，以及对Volume的&lt;strong&gt;分块&lt;/strong&gt;管理。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gv5ooyhloxj61150u043702.jpg" alt="image-20211006164428466" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Store的数据访问一般是以Needle维度进行的。为了加速文件寻址，一般会在内存中维护一个用于寻址元信息的Map：&lt;code&gt;key/alternate key =&amp;gt; needle's flag/offset/other attribute&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这些Map最后会被持久化到磁盘中的&lt;strong&gt;Index File&lt;/strong&gt;中，做为In-Memory Mapping的一种Checkpoint存在，用于Crash后寻址元数据的快速恢复。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gv5put6m7qj60u40jc0u102.jpg" alt="image-20211006172431986" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gv5puqgvgcj60te0dk0ua02.jpg" alt="image-20211006172515258" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每个Volume会维护一个自己的In-Memory Mapping和Index File&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在我们更新In-Memory Mapping的时候（比如修改文件、新增文件），会异步更新Index File。但在文件删除时，我们只异步标记文件删除，而不会修改Index File。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Index只是作为加速查找的手段，无Index的Needles依旧是可以被寻址的，故而上述策略是有效的。但是由于异步更新和不删除对应文件的Index的设计，引入了Orphan Index和Deleted Index的问题。&lt;/p&gt;
&lt;p&gt;Orphan Index是指无索引记录的Needle，一般Store会检查出这些Needle并为其添加Index。&lt;/p&gt;
&lt;p&gt;Deleted Index一般就直接在查询是检测出文件是Deleted的，并且是Mark Deleted的状态，此时会告知上层为查询到文件，同时及时更新In-Memory Mapping。这样的设计加速了文件NotFound的情况下的查询时间。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="工作负载"&gt;工作负载&lt;/h3&gt;
&lt;h4 id="读"&gt;读&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;(Logical Volume ID, key, alternate key, cookies) =&amp;gt; photo&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;当接收到从Cache收到读请求时，Store会去查询In-Memory Mapping查询对应的Needle。如果查询到，则根据volume + offset获取文件数据，并校验文件的cookie和完整性；否则返回错误。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cookie是在Directory生成URL的时候随机生成的字符串，在寻址的时候带上并校验可以有效防止恶意攻击。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="写"&gt;写&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;(Logical Volume ID, key, alternate key, cookies， data) =&amp;gt; result&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Haystack不支持文件的覆盖写入，只支持追加写。当收到写请求时，Store会异步Append文件数据到Needle中并更新In-Memory Mapping。如果这是一个老文件，那么Directory会更新它保存的元数据，以便后续的访问不会访问老版本的文件。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;除了Directory的元信息之外，怎么去判断数据版本的新旧？&lt;/p&gt;
&lt;p&gt;答案是根据volume以及offset。老的volume会被冻结为ReadOnly，新的volume的写入是追加的，所以offset大的必然更新。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="删除"&gt;删除&lt;/h4&gt;
&lt;p&gt;采取&lt;strong&gt;Mark Delete + Compact GC&lt;/strong&gt;的方式处理删除请求。&lt;/p&gt;
&lt;h3 id="容错设计"&gt;容错设计&lt;/h3&gt;
&lt;p&gt;对于Store使用&lt;strong&gt;监控 + 热备&lt;/strong&gt;的方式，Directory和Cache可以使用Raft等一致性算法保证数据副本一致与容错。&lt;/p&gt;
&lt;h2 id="优化"&gt;优化&lt;/h2&gt;
&lt;p&gt;优化手段主要有三点：Compaction、Batch Load、In Memory。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;优化的抽象：异步、批处理、缓存&lt;/li&gt;
&lt;li&gt;要发现主要问题，比如大量小文件最主要的问题是元数据带来的管理负担。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf"&gt;Finding a needle in Haystack: Facebook’s photo storage&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-Bigtable</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-bigtable/</link><pubDate>Thu, 16 Sep 2021 22:54:59 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-bigtable/</guid><description>&lt;p&gt;之前在网上找到了别人翻译的BigTable论文，就顺手保存了下来，但一直没开始看。最近发现BigTable和目前组内做的项目有很多设计上相似的地方，于是用周末的时间快速的阅读了一遍。&lt;/p&gt;
&lt;p&gt;这是Google分布式三大论文的最后一篇，本不属于MIT6.824课程的阅读材料的。但姑且先如此分类。&lt;/p&gt;
&lt;p&gt;笔记依旧不深究具体细节，仅仅记录对问题的思考以及设计的思路。&lt;/p&gt;
&lt;h2 id="介绍"&gt;介绍&lt;/h2&gt;
&lt;p&gt;Bigtable 是一个分布式的&lt;strong&gt;结构化数据&lt;/strong&gt;存储系统，构建于GFS之上，用于存储大量的结构化，半结构化数据。它属于NoSql数据存储，优势在于可拓展性和性能，以及基于GFS上的可靠容错。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Design Goal：Wide Applicability、Scalability、High Performance、High Availability&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="数据模型"&gt;数据模型&lt;/h2&gt;
&lt;p&gt;Bigtable的数据模型属于No Scheme的设计，仅提供了一个简单的数据模型，它将所有的数据视为字符串，数据的编解码交由上层业务方决定。&lt;/p&gt;
&lt;p&gt;实际上，Bigtable 是一个&lt;strong&gt;稀疏的、分布式的、持久化存储的多维度排序 Map&lt;/strong&gt;。Map的&lt;strong&gt;索引&lt;/strong&gt;是&lt;strong&gt;Row Key，Column Key以及TimeStamp&lt;/strong&gt;。Map中的&lt;strong&gt;每一个Value就是一个无结构的Byte数组&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="code-block" data-language="go"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;go&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 映射抽象&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 一个Row Key实际上是 {Row , Column, Timestamp} 组成的多维结构。&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gufarm0ykaj615k0d0jts02.jpg" alt="image-20210913205832997" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;论文对数据模型原文阐述如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A Bigtable is a sparse, distributed, persistent multidimensional sorted map.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sparse，实际上是说同个Table中的Column属性是可以置空的，并且置空比较常见。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Row&lt;/th&gt;
&lt;th&gt;Columns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Row1&lt;/td&gt;
&lt;td&gt;{ID，Name，Phone}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row2&lt;/td&gt;
&lt;td&gt;{ID，Name，Phone，Address}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row3&lt;/td&gt;
&lt;td&gt;{ID，Name，Phone，Email}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Distributed，此处分布式关键在于拓展性和容错性，也就是&lt;strong&gt;Replication&lt;/strong&gt;和&lt;strong&gt;Sharding&lt;/strong&gt;。Bigtable通过GFS的Replica实现副本容错，使用&lt;strong&gt;Tablet&lt;/strong&gt;对数据分区，以实现拓展性。&lt;/p&gt;
&lt;p&gt;Persistent Multidimentsional Sorted，持久化说明最终需要数据落盘，Bittable相关的设计有WAL，LSM优化前台读写时延，优化落盘速度。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BigTable的开源实现就是HBase，是一种行列数据库&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="rows"&gt;Rows&lt;/h3&gt;
&lt;p&gt;Bigtable通过行关键字的字典序来组织数据，Row Key可以是任意的字符串。对于单行的读写操作是原子的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;字典序的好处在于可以将相关的行记录聚合&lt;/p&gt;
&lt;p&gt;可以参考Mysql的实现，利用undo log的方式实现行操作的原子性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="column-family"&gt;Column Family&lt;/h3&gt;
&lt;p&gt;Column Keys 组成的集合叫做 Column Family，一个Column Family下的数据往往属于同一种类型。&lt;/p&gt;
&lt;p&gt;一个Column Key由 Column Family : Qualifier组， 列族的名字必须是可打印的字符串,而限定词的名字可以是任意的字符串。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;论文中有提到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Access control and both disk and memory accounting are performed at the column-family level.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原因在于，业务方取得数据实际上更多的是以Column为单位取得相关的数据，比如读取网页content等操作。实践上，往往也将列数据压缩存储。基于此，Column Family显然是一个比Row更合适的Level。&lt;/p&gt;
&lt;p&gt;如我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据) 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="time-stamp"&gt;Time Stamp&lt;/h3&gt;
&lt;p&gt;TimeStamp主要是为了维护不同时间的不同数据版本，属于一种逻辑时钟。同时它也作为索引，不同版本的数据可以通过时间戳索引查询。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;设计上，一般按时间新旧排序，推测在数据版本少的时候可以利用一个指向上一个时间版本的指针来维护数据时间视图，在数据版本多的时候需要进化为索引结构。&lt;/p&gt;
&lt;p&gt;显然基于时间戳必然有范围查询的需求，选择可排序数据结构作为索引比较合&lt;/p&gt;
&lt;p&gt;但TimeStamp需要Table额外维护一个数据版本视图，带来一定的管理负担。一般的解决方法是限制数据版本的数量，将过期的数据进行GC处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="tablet"&gt;Tablet&lt;/h3&gt;
&lt;p&gt;Bigtable在存储设计上，采用的是&lt;strong&gt;range-based&lt;/strong&gt;的&lt;strong&gt;数据分片方式&lt;/strong&gt;，而Tablet是Bigtable中data sharding 和 load balance的基本单位。&lt;/p&gt;
&lt;p&gt;Tablet其实就是有若干个Row组成的一块数据，并由一个Tablet Server进行管理。行数据在Bigtable系统内最终保存在一个Tablet上，并在适当的时候进行Tablet拆分合并，在Tablet Server之间负载均衡。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Range Base的分片方式的好处是有利于范围查询，与之相对的是Hash分片的方式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="sstable"&gt;SSTable&lt;/h3&gt;
&lt;p&gt;SSTable 是一个&lt;strong&gt;持久化的、排序的、不可更改&lt;/strong&gt;的Map 结构,而 Map 是一个 key-value 映射的数据结构,key 和 value 的值都是任意的 Byte 串。&lt;/p&gt;
&lt;p&gt;Tablet实际上是Bigtable系统中对外的存储单位，实际上，内部的存储文件是Google SSTable格式的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从内部看，SSTable 是一系列的数据块(通常每个块的大小是 64KB)，通过索引加速定位数据。读取时先读索引，二分查索引，在读取数据块。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="api"&gt;API&lt;/h3&gt;
&lt;p&gt;原文中的API如下，主要是为了体现和RDB不同的地方。&lt;/p&gt;
&lt;div class="code-block" data-language="cpp"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;cpp&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Writing to Bigtable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Open the table Table *T = OpenOrDie(&amp;#34;/bigtable/web/webtable&amp;#34;); // Write a new anchor and delete an old anchor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;RowMutation&lt;/span&gt; &lt;span class="nf"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;com.cnn.www&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anchor:www.c-span.org&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;CNN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anchor:www.abc.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Operation&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Reading from Bigtable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Scanner&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ScanStream&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FetchColumnFamily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anchor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SetReturnAllVersions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;com.cnn.www&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s %s %lld %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ColumnName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;MicroTimestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="架构设计"&gt;架构设计&lt;/h2&gt;
&lt;h3 id="外部组件"&gt;外部组件&lt;/h3&gt;
&lt;p&gt;Bigtable是基于Google内部其他组件之上构建的，这极大的简化了Bigtable的设计。&lt;/p&gt;
&lt;h4 id="gfs"&gt;GFS&lt;/h4&gt;
&lt;p&gt;GFS是Bigtable底层的GFS，可以提供Replication，提供一定的数据容错性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以参考上一篇笔记内容&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="chubby"&gt;Chubby&lt;/h4&gt;
&lt;p&gt;Chubby是一个高可用的分布式锁组件，它提供了一个NameSpace，其中的目录和文件均可作为一个分布式锁来使用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;高可用是指维护了多个可提供服务副本，并使用paxos算法保证副本间的一致性。同时使用租约机制，防止失效的Chubby客户端一直持有锁。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为什么需要Chubby？它的作用是什么？&lt;/p&gt;
&lt;p&gt;原文中提到的功能有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;存储Bigtable的Column Family信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储ACL。ACL 是一种机制，用于定义用户访问存储对象的权限&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储了 元数据的起始位置，也就是Root Tablet的位置信息&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bigtable使用了一个三层的类B+树的存储数据结构，Chubby中保存了Root Tablet的位置信息，Root Tablet保存了其他MetaData的Tablet信息，其他MetaData的Tablet则保存着其他用户的Tablet集合信息。&lt;/p&gt;
&lt;p&gt;在Bigtable启动时，会先从Chubby中获取Root tablet，然后再获取其他映射信息。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tablet Server的生命周期监控&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tablet Server在启动时，会在Chubby&lt;strong&gt;指定目录&lt;/strong&gt;下生成一个唯一名称的文件，并获取此文件的排它锁。当Tablet Server失效时，会释放锁。&lt;/p&gt;
&lt;p&gt;Master通过监控这个目录中的文件以及持有锁的状态，来监控集群中的Tablet的状态以及配置变更&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总结一下，Chubby的功能其实主要分为两类。一是利用Chubby作为高可用高可靠的存储节点来存储关键元信息；二是利用其提供的分布式锁服务来管理维护存储节点（Tablet Server）。&lt;/p&gt;
&lt;p&gt;在GFS中，这些功能其实是由Master去负责管理维护的。在Bigtable中，由Chubby接管，这样简化了Master的设计，并减轻了Master的负载。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;抽象意义上，Chubby也能视为Master节点的一部分。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="内部组件"&gt;内部组件&lt;/h3&gt;
&lt;h4 id="master"&gt;Master&lt;/h4&gt;
&lt;p&gt;Bigtable也属于Master-Slave的架构，这点个GFS以及MR的设计很像。不同之处在于，Bigtable将元数据交给了Chubby + Tablet Server去存储与管理，Master只负责调度需要的信息，不存储Tablet位置信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tablet分配，GC；Tablet Server监控，负载均衡；表的元数据修改等&lt;/p&gt;
&lt;p&gt;所以调度需要的信息包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;所有Tablet的信息，用于分析分配和分布情况&lt;/li&gt;
&lt;li&gt;Tablet Server的状态信息，判断是否满足分配条件&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4 id="tablet-server"&gt;Tablet Server&lt;/h4&gt;
&lt;p&gt;Tablet Server 管理一系列的Tablet，负责处理Tablet的数据读写，以及Tablet的拆分合并等操作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Master不保存元信息，Client读取信息直接与Chubby和Tablet Server交互。&lt;/p&gt;
&lt;p&gt;Tablet的拆分由Tablet Server主动发起，并及时通知Master。这一步可能会消息丢失，所以最好使用WAL+重试的方式保证操作不丢失&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="client-sdk"&gt;Client SDK&lt;/h4&gt;
&lt;p&gt;作为Bigtable的接入层，业务方使用ClientSDK接入Bigtable。Client SDK作为Bigtable的入口，优化的关键在于怎么样可以减少获取元数据的次数。一般使用cache以及prefetch的思路去减少网络的交互，充分利用时间和空间局部性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;缓存的使用势必会带来不一致问题，需要设计合适的方案解决此问题。如不一致时重新寻址。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="存储相关"&gt;存储相关&lt;/h2&gt;
&lt;h3 id="映射关系-寻址"&gt;映射关系 、寻址&lt;/h3&gt;
&lt;p&gt;Bigtable的数据实际上是由(Table, Row, Column)三元组唯一确定的，保存在Tablet中，Tablet最终保存在GFS SSTable中。&lt;/p&gt;
&lt;p&gt;Tablet逻辑上可以理解为Bigtable的落盘实体，实际上是交由Tablet Server去管理维护，那么这样就需要Bigtable去维护一定的映射关系。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;基于原文描述，可能需要维护的映射：&lt;/p&gt;
&lt;div class="code-block" data-language="cpp"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;cpp&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Table&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;Tablets&lt;/span&gt; &lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Tablet&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tablet&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Tablet&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt; &lt;span class="c1"&gt;// Tablet contains
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Tablet&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="c1"&gt;// where Tablet shores
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;SSTable&lt;/span&gt; &lt;span class="c1"&gt;// where the tablet stores in GFS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Indexes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Row&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tablet&lt;/span&gt; &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SSTable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bigtable通过&lt;code&gt;Root Tablet + METADATA Table&lt;/code&gt;进行Tablet寻址。Chubby存储了Root Tablet的位置信息，METADATA Table则由Tablet Server负责维护。&lt;/p&gt;
&lt;p&gt;Root Tablet中保存了其他METADATA Tablet的位置信息。而METADATA Table的每一个Tablet包含了一系列的User Tablets的位置信息（可以理解为UserTable handle）, 每个Tablet的位置信息保存在METADATA的Row中。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;METADATA Table 中的Row :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(TableID,encoding of last row in Tablet) =&amp;gt; Tablet Location&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guhk1wuu1pj60q80ge75k02.jpg" alt="image-20210915142508074" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;系统采用了类似B+树的三层结构来维护tablet location信息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="调度和监控"&gt;调度和监控&lt;/h3&gt;
&lt;h4 id="调度"&gt;调度&lt;/h4&gt;
&lt;p&gt;其实主要就是对Tablet的调度，包括分配和负载均衡。&lt;/p&gt;
&lt;p&gt;其实两种归根到底都是分配问题。在任何一个时刻, 一个 Tablet 只能分配给一个 Tablet 服务器。Master保存了Tablet Server 相关的状态信息，当有Tablet需要分配时，就发送请求将Tablet分配出去。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Master不维护寻址相关的状态信息，但需要维护Tablet Server的状态信息（持有的Tablets数量、状态、剩余资源等），可以通过心跳周期上报到Master。所以说，Master其实是无状态的，负载也比较轻。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="监控"&gt;监控&lt;/h4&gt;
&lt;p&gt;监控是由Chubby + Master来完成的。&lt;/p&gt;
&lt;p&gt;Tablet Server在启动时，会在Chubby&lt;strong&gt;指定目录&lt;/strong&gt;下生成一个&lt;strong&gt;唯一名称的文件&lt;/strong&gt;，并获取此文件的排它锁。当Tablet Server断开连接，lease失效后会自动释放文件锁。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tablet Server通过唯一文件决定是否对外提供服务。此文件可能被Master删除&lt;/p&gt;
&lt;p&gt;Tablet Server可能由于网络因素重新连接，此时只要文件存在，Tablet重连时又会重新尝试去获取这个文件的排他锁。&lt;/p&gt;
&lt;p&gt;如果不存在，当原Tablet Server重连是，应当自动退出集群。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Master首先要在Chubby获取一个唯一文件排他锁保证Master节点的唯一性，并指定一个Tablet Server文件目录。&lt;/p&gt;
&lt;p&gt;然后Master通过不断监控这个目录中的文件以及持有锁的状态，来监控集群中的Tablet的状态以及配置变更，并获取正在运行的Tablet Server列表。&lt;/p&gt;
&lt;p&gt;一但发现有Tablet Server失效，Master就会把Chubby属于原Tablet Server的唯一文件删除，并把原来这个Tablet Server维护的Tablet集合重新分配给其他Tablet Server。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tablet Server失效有两种情况，一种是无法与Chubby连接，二是成为孤岛或者宕机。前者可以通过文件锁状态判断，后者通过Master发送心跳。其余情况，可能是Chubby无法提供服务。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;之后在和所有Active的Tablet Server进行通信，获取Tablet Server的状态信息；扫描METADATA表获取未分配的Tablet信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;监控的目的其实就是为了调度。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="compaction"&gt;Compaction&lt;/h2&gt;
&lt;p&gt;Bigtable对外提供读写服务，并且使用了LSM的结构对写性能进行优化。对于一个写操作，首先通过Chubby中保存的ACL信息，判断用户的权限；通过之后再以WAL的方式顺序写记录操作再CommitLog和Memtable中，并且最后会被持久化到SSTable中；&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guhk6llq76j60uk0lsq4402.jpg" alt="image-20210915195135522" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;由图中可以看出，memtable是保存在内存中的，无法无限的增加。所以Bigtable在memtable增长到一定大小的时候会进行一次Minor Compaction，将memtable的数据写入一个SSTable中，并写入GFS。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上，memtable在转为SSTable之前会先转换成immutable memtable，并生成新的memtable支持前台写入。这样的中间状态其实是为了Minor Compaction不影响前台写。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guill1zltrj615g0p8adp02.jpg" alt="image-20210916172932210" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;Bigtable在处理写入数据时提出了&lt;strong&gt;Compaction&lt;/strong&gt;概念，加速前台写。所有的前台随机写被转换为顺序写。并在后台的Compact进程中再次对数据进行写入。以读写放大为代价，优化前台感知到的写性能。&lt;/p&gt;
&lt;p&gt;所谓Compaction，本质上就是对数据的一次再次扫描，在扫描写入过程中，对数据进行合并压缩和GC。&lt;/p&gt;
&lt;p&gt;一般Compaction以多个不可变数据作为输入，Compact之后会将数据重新写入另一个新的只读有序结构(如SSTable)或者随机写落盘。&lt;/p&gt;
&lt;p&gt;前台写对Compaction的参与数据不影响，这使得Compaction步骤原生支持并行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;原文中提到了三种Compaction：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Minor Compaction&lt;/strong&gt;：将memtable转化成SSTable的过程就是Minor Compaction，写入过程中会丢弃被删除的数据，并只保留数据的终态。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Minor Compaction的目的在于减少Tablet Server的内存使用，以及CommitLog的大小。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Merge Compaction&lt;/strong&gt;：Memtable和SSTable一起Compact得到一个新的SSTable&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Major Compaction&lt;/strong&gt;：多个SSTable也可能需要Compact成一个SSTable
但对于读请求，我们可能需要聚合Memtable以及一定的SSTable来做读查询，因为查询的数据可能存在于memtable或者SSTable中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原文中引入了&lt;strong&gt;二级缓存&lt;/strong&gt;和&lt;strong&gt;Bloom Filter&lt;/strong&gt;来加速读查询。&lt;/p&gt;
&lt;p&gt;Tablet Server有两级缓存。第一层是&lt;strong&gt;扫描缓存&lt;/strong&gt;，主要缓存从SSTable读取过的的KV pair；第二级是&lt;strong&gt;Block缓存&lt;/strong&gt;，缓存读取的SSTable Block。&lt;/p&gt;
&lt;p&gt;对于经常要重复读取相同数据的应用程序来说,扫描缓存非常有效；对于经常要读取刚刚读过的数据附近的数据的应用程序来说，Block 缓存更有用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bloom Filter&lt;/strong&gt;简单来说就是可以判断某个Key是否不存在。&lt;/p&gt;
&lt;p&gt;可以为全局或者每个Tablet Server，以及SSTable分别维护一个Bloom Filter，用于加速读查询，减少到SSTable的查询；对于可能存在的Key，利用二分法查询&lt;/p&gt;
&lt;h2 id="优化"&gt;优化&lt;/h2&gt;
&lt;p&gt;除了上文中说的到优化方式，原文还提到了几种优化手段。&lt;/p&gt;
&lt;h3 id="局部性"&gt;局部性&lt;/h3&gt;
&lt;p&gt;通过配置或者自动等方式，把高频使用的列数据组合生成一个SSTable存储，减少分开Fetch的时间。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;空间换时间，充分利用局部性原理&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="压缩"&gt;压缩&lt;/h3&gt;
&lt;p&gt;将SSTable中的分块进行二级压缩处理。本质其实是为了减少网络传输的带宽和时延，但需要额外的压缩和解压计算。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;分块压缩是为了减少编解码的时间以及提高并行效率&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="commitlog-设计"&gt;CommitLog 设计&lt;/h3&gt;
&lt;p&gt;关键其实是Log的粒度问题。Tablet粒度下，会有大量的Log文件。在并行写入GFS中会有大量的磁盘Seek操作，并且不利于Batch化。集群单独一个CommitLog会带来恢复上的高复杂度。&lt;/p&gt;
&lt;p&gt;Bigtable中是每个Tablet Server一个Commit Log。但由于Tablet Server维护了多个Tablet，这就使得Bigtable必须维护LogEntry到TabletID的映射以及顺序，以便在恢复时使用。对一个Tablet的数据恢复可能会导致扫描整个CommitLog，&lt;/p&gt;
&lt;p&gt;此处Bigtable的优化是将Log按照(Table, row, log seq num)并行分块排序，使得同一个Tablet的LogEntry聚合。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;不要过渡设计，Simple is Better Than Complex。&lt;/p&gt;
&lt;p&gt;对于分布式服务，集群监控非常重要。Google三篇论文中对集群状态的监控都是不可缺少的一环。监控的意义也在于支持集群的调度。&lt;/p&gt;
&lt;p&gt;设计时不要对其他系统做出任何假设，出现的不仅仅是常见的网络问题，现实中可能会遇到各类问题。&lt;/p&gt;
&lt;p&gt;利用后台处理加速前台感知。利用简单的机制处理前台请求，再开启后台进程善后。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://zh.wikipedia.org/wiki/Bigtable"&gt;wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf"&gt;Bigtable&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/xybaby/p/9096748.html"&gt;典型分布式系统分析：Bigtable&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://zhuanlan.zhihu.com/p/181498475"&gt;LSM树详解&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-GFS</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-gfs/</link><pubDate>Thu, 09 Sep 2021 00:44:24 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-gfs/</guid><description>&lt;p&gt;之前对GFS的理解并不能支持我写出满意的内容，于是一直搁置。最近刚转岗某司存储部门实习，回想起此文，于是在无所事事之时写下这个笔记。&lt;/p&gt;
&lt;p&gt;这是鸽了许久的Distributed System学习笔记的第二章。笔记并没有记录详细的具体操作，仅仅记录了对问题的思考与设计思路。&lt;/p&gt;
&lt;h2 id="介绍"&gt;介绍&lt;/h2&gt;
&lt;p&gt;GFS是Google使用的分布式文件系统。它使用大量机器为数据密集型应用构建了一个可靠的分布式文件服务。&lt;/p&gt;
&lt;p&gt;最早出现在在2003年发表的一篇论文。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;首先,组件失效被认为是&lt;strong&gt;常态事件&lt;/strong&gt;，而不是意外事件。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;使用大量的廉价机器搭建可靠的服务。每台机器一定概率失效，整体失效概率满足二项分布。&lt;/p&gt;
&lt;p&gt;基于此，我们需要为系统组件设计容错保障机制，使得组件能够被监测，能够发现故障，并及时自动fail over，保证系统的可用性（HA)。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start="2"&gt;
&lt;li&gt;文件非常&lt;strong&gt;巨大&lt;/strong&gt;。数 GB 的文件非常普遍。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;相对于传统标准而言。对于一个分布式存储系统，我们应该尽量使用大文件为粒度对文件进行维护。&lt;/p&gt;
&lt;p&gt;过多的小文件，意味需要更多的inode等数据结构记录元数据，有效的磁盘存储空间会减少。&lt;/p&gt;
&lt;p&gt;其次，分布式文件寻址一般需要网络通信，寻址结果需要缓存，更多的文件意味着更多的缓存项，这也会造成空间浪费。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start="3"&gt;
&lt;li&gt;绝大部分文件的修改是采用在文件尾部追加数据,而不是覆盖原有数据的方式，对文件的随机写入操作在实际中几乎不存在。一旦写完之后,对文件的操作就只有读,而且通常是按&lt;strong&gt;顺序读&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;这是GFS对使用场景的考量和trade_off。&lt;/p&gt;
&lt;p&gt;Google内部大部分操作都是 append，因此 GFS 系统优化的中心也放在 record append 操作上，对于&lt;strong&gt;随机写则不保证数据的一致性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;同时顺序IO写也会比随机IO的读写性能好很多。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start="4"&gt;
&lt;li&gt;第四，应用程序和文件系统API的协同设计提高了整个系统的灵活性&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;协同设计，提供类似POXIS文件系统API接口。提供客户端Lib帮助业务方屏蔽底层细节。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="设计预期"&gt;设计预期&lt;/h2&gt;
&lt;h3 id="存储能力"&gt;存储能力&lt;/h3&gt;
&lt;p&gt;预期会有几百万文件,文件的大小通常在100MB或者以上。数个GB大小的文件也是普遍存在,并且要能够被有效的管理。&lt;/p&gt;
&lt;p&gt;系统也必须支持小文件,但是不需要针对小文件做专门的优化。&lt;/p&gt;
&lt;h3 id="工作负载"&gt;工作负载&lt;/h3&gt;
&lt;h4 id="读工作负载"&gt;读工作负载&lt;/h4&gt;
&lt;p&gt;主要由&lt;strong&gt;大规模流式读取&lt;/strong&gt;和&lt;strong&gt;小规模随机读取&lt;/strong&gt;组成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大规模流式读取：大规模的磁盘顺序IO读取数据&lt;/li&gt;
&lt;li&gt;小规模随机读取：小规模磁盘随机IO（任意偏移量）读取数据&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;对于小规模的随机读有一定的优化，比如对读请求排序，再批量处理，而非来回随机读取。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="写工作负载"&gt;写工作负载&lt;/h4&gt;
&lt;p&gt;主要是大规模的顺序写操作。将数据append到文件的末尾。&lt;/p&gt;
&lt;p&gt;系统必须高效的、行为定义明确的实现多客户&lt;strong&gt;端并行追加数据到同一个文件&lt;/strong&gt;里的语意。这要求系统提供并发写支持。操作的原子性以及同步开销是主要性能衡量指标。&lt;/p&gt;
&lt;h3 id="带宽与延迟"&gt;带宽与延迟&lt;/h3&gt;
&lt;p&gt;高&lt;strong&gt;持续&lt;/strong&gt;带宽（High sustained bandwidth）比低延迟更重要&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这是由GFS的使用背景决定的。大多数GFS的业务方更关心高效率的处理数据，而非延迟。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="容错设计"&gt;容错设计&lt;/h3&gt;
&lt;p&gt;系统必须持续监控自身的状态,它必须将组件失效作为一种常态,能够迅速地侦测、冗余并恢复失效的组件。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;参考背景一&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="操作与接口"&gt;操作与接口&lt;/h3&gt;
&lt;p&gt;对外提供了传统的文件系统接口，但是出于效率和使用性的角度，并没有实现标准的文件系统 POSIX API。&lt;/p&gt;
&lt;p&gt;目录树、文件增删改查、快照、Atomic Record Append。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;快照指创建文件和目录树的副本&lt;/p&gt;
&lt;p&gt;Atomic Record Append指保证原子性的记录追加操作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="架构设计"&gt;架构设计&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;核心问题是：接入，寻址，管理，容错，一致。这些问题决定我们怎样去设计一个DFS的整体架构&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GFS采用Master-Slave架构。&lt;/p&gt;
&lt;p&gt;一个GFS集群包含一个单独的Master Node，多台Chunk Server，以及若干Client。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;此处Master和Chunk Server实际上都是逻辑上的概念，实际上对应的只是用户态的一个进程，而非指具体物理机器。&lt;/p&gt;
&lt;p&gt;单Master设计简化了GFS的设计，但有一定的单点故障的风险，属于设计上的trade_off&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gu6y6qm5t0j61i40nojuk02.jpg" alt=" image-20210906153847451" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;GFS通过lib的形式提供给业务方使用、接入GFS。&lt;/p&gt;
&lt;p&gt;文件数据最终以chunk为单位保存在chunk server的磁盘中，并且chunk以replica的形式提供可靠服务。&lt;/p&gt;
&lt;p&gt;master管理着GFS上所有的元数据（目录树，mapping，chunk metadata，etc）以及相关系统行为（监控，GC，snapshot，etc）。&lt;/p&gt;
&lt;p&gt;用户通过Client，以filename + offset访问Master获取chunk元信息，再根据原信息访问对应chunk server获取具体的数据，执行操作。&lt;/p&gt;
&lt;!-- mermaid
sequenceDiagram
Client-&gt;&gt;Lib: filename + offset
Lib-&gt;&gt;Master: chunk index + offset
Master-&gt;&gt;Client:chunk handle
Client -&gt;&gt; ChunkServer:chunk location + chunk id + offset
}}
--&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gu9234vb9nj61ej0u0gq702.jpg" alt="" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;h2 id="组件设计"&gt;组件设计&lt;/h2&gt;
&lt;h3 id="client"&gt;Client&lt;/h3&gt;
&lt;p&gt;Client在GFS中实际上只是使用了GFS SDK/LIB的一个应用进程。GFS提供SDK/LIB让业务方接入GFS，并尽可能的屏蔽底层细节，让业务方对GFS无感知。&lt;/p&gt;
&lt;p&gt;因此，Client作为接入层需要提供以下功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;缓存&lt;/strong&gt;：缓存从Master处获取的元信息，减少网络通信的次数。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;缓存也可能会导致元信息不一致的问题，需要设计合理的机制。论文中并未详细说明。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;封装&lt;/strong&gt;：屏蔽底层操作，如文件操作失败重试，命令spilt，数据checksum以及校验等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;操作优化&lt;/strong&gt;：batch，load balance等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;映射&lt;/strong&gt;：将参数filename + offset 映射为chunk index + offset&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="master"&gt;Master&lt;/h3&gt;
&lt;p&gt;Master管理着GFS所有的元数据，在GFS中属于计算层，负责文件以及文件系统的调度与管理。&lt;/p&gt;
&lt;p&gt;其中元信息其实是保存在Master的内存中的，并未持久化，通过chunk server的上报来维护更新。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;此处也是Trade_off, 简化Master和ChunkServer交互设计，但可能有数据丢失的风险，但为Master扩大内存以便为GFS提供其拓展性，代价比较小，设计者认为这也是可以接受的。&lt;/p&gt;
&lt;p&gt;同时，设计者认为chunk的位置实际上是由chunk server决定，故而不对元信息进行持久化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;基于此，Master需要提供以下功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;监控机制&lt;/strong&gt;：Master节点管理这Chunk Server，并且元信息只保存在内存中，并且ChunkServer和其保存的文件均可能失效，因此需要监控Chunk Server的状态，并收集其保存的文件信息，并持续监控。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;一般利用心跳机制，可以双向也可以只从ChunkServer到Master。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目录树管理&lt;/strong&gt;：文件以分层目录的形式管理，因此需要管理名称空间，也需要考虑到并发安全等问题&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;一般可以将目录树前缀压缩，减少磁盘空间使用。&lt;/p&gt;
&lt;p&gt;加锁，设计合理的加锁策略，提高并发度。比如，写文件时，对文件目录加读锁，只对文件加写锁&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;映射管理&lt;/strong&gt;：Master接收Client传入的参数，返回其需要的元信息。需要维护两种映射关系：
&lt;ul&gt;
&lt;li&gt;Table1：filename =&amp;gt; list of chunk ID (nv)&lt;/li&gt;
&lt;li&gt;Table2：chunk ID =&amp;gt; chunk handler&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;chunk handler是一个数据结构，包含&lt;/p&gt;
&lt;div class="code-block" data-language="cpp"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;cpp&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;ChunkHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;ChunkLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="c1"&gt;// 标识replica所在的机器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 数据的逻辑时钟，标记数据版本
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 用于标记primary chunk，用于分配操作顺序
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;lease&lt;/span&gt; &lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 租约过期时间，防止ChunkServer长期持有primary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;此处的映射实际上就是Master需要维护的数据结构（元信息），其中有一部分是需要持久化保证重启不丢失，以nv（non-volatile)标记。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Table1适合使用HashMap作为索引结构去查询，Table2则更适合使用B+Tree等数据结构做索引，因为其应该会设计范围查询。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;容错&lt;/strong&gt;：Master可能会失效，需要容错机制。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Master使用Raft多副本容错，影子热备，定时CheckPoint备份元信息，以便快速回复内存数据、operation log记录对元信息的修改做持久化记录，WAL保证操作不丢失。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;系统调度&lt;/strong&gt;：集群上chunk replica数量会不满足容错要求，这个时候就要新建副本。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;如Chunk Server失效，配置变更&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;数据被删除或者发现孤儿Chunk（不包含数据的Chunk）时，Master需要负责GC。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一般删除的流程为：标记删除+ 延迟清理，降低前台处理时延，但会有一段时间窗口，占用磁盘的有效空间，也是trade_off&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;快照创建时，也需要Master的参与。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;会先释放chunk的lease，再以COW的方式生成快照&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Chunk数据的分布可能不满足，Master需要调度，使得数据分布满足可靠容错的要求。&lt;/p&gt;
&lt;p&gt;分配Lease，选择Primary Chunk，管理Version等&lt;/p&gt;
&lt;h3 id="chunk-server"&gt;Chunk Server&lt;/h3&gt;
&lt;p&gt;ChunkServer属于存储层，负责具体的数据以Linux文件的格式储存。&lt;/p&gt;
&lt;p&gt;Client从Master获取元信息之后，再找到对应的ChunkServer，通过Chunk Index + Offset对文件进行寻址，再对文件进行操作。同时它需要及时上报ChunkServer的运行信息，以及它维护的Chunk的信息，以便Master维护系统的元数据。&lt;/p&gt;
&lt;h2 id="系统内部设计与交互"&gt;系统内部设计与交互&lt;/h2&gt;
&lt;p&gt;讲一件上面没有涉及的关键设计思路，并不涉及具体细节&lt;/p&gt;
&lt;h3 id="chunk"&gt;Chunk&lt;/h3&gt;
&lt;p&gt;chunk属于GFS管理数据逻辑最小单元，设计Chunk的关键是Chunk Size。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Chunk Size过小，在相同有效数据下，会产生大量元信息，加重Master的管理（元数据，cache，调度等）负担；其次，加重了Client和Master之间的网络通信的次数；没有很好的利用局部性原理。&lt;/p&gt;
&lt;p&gt;ChunkSize过大，导致对数据的管理粒度过大，空间碎片化，降低磁盘优先使用；其次，被频繁访问的数据可能集中在同一个Chunk上，造成热点问题，并发读写时同步开销大。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;基于上述原因，GFS中的ChunkSize最终被定为64MB，用一个64位全局唯一ID标识。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目前的DFS对小文件的优化实际上也就是将小文件聚合管理，减少其Master处元信息，转而将其元信息以payload的形式写入更小数据单元的header中，以便后续读写。&lt;/p&gt;
&lt;p&gt;还有一个问题是，为什么大文件要分块管理？分块主要是为了提高大文件处理并发度。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="lease"&gt;Lease&lt;/h3&gt;
&lt;p&gt;Lease机制主要是为了保证多副本间数据变更的一致性。&lt;/p&gt;
&lt;p&gt;当有并发顺序写请求时，Master会分配Chunk Lease给Chunk涉及的一个ChunkServer上，作为Primary节点。被选中的Primary节点会对并发写请求进行排序，安排处理数据的顺序，保证并发数据安全。并将顺序返回给Master节点。&lt;/p&gt;
&lt;p&gt;只有再有Master节点将处理数据通知其余的Secondaries，它们只能按照此顺序处理数据。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这样的设计减少了Master的管理开销，也保证了线程数据安全，将排序交由ChunkServer处理。但ChunkServer可能随时会失效，需要防止Lease被失效的机器长期占有，故而定一个Lease Time，限制单次Lease使用的时间。&lt;/p&gt;
&lt;p&gt;Primary节点可以通过申请延长Lease时间，满足数据处理的需求。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;Version主要是用来标记数据的版本，在分配Lease，选择出primary后递增并告知primary，收到ack后再持久化记录后生效。只有最新版的数据才能证有效，失效的数据需要及时处理。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ChunkServer可能短暂的失效重连，在这之间对数据的操作就可以通过Version来判断数据的新旧。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="控制流和数据流"&gt;控制流和数据流&lt;/h3&gt;
&lt;p&gt;GFS中控制流和数据流是解耦的。数据流和控制流分开推送到所有设计的ChunkServer中，最后再按照Primary决定的处理顺序执行控制指令。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;解耦的好处是数据流可以基于网络拓扑规划，提高机器带宽利用率，避免网路欧瓶颈和高延时。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;数据流实际上是以Pipeline的形式推送到所有相关的ChunkServer中的。推送到机器上后，保存在LRU缓存中，再由此机器推送到其他机器，充分利用每一台机器的资源。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gu94bb7jxjj60v90u0tak02.jpg" alt="image-20210908124156337" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;h3 id="数据安全性"&gt;数据安全性&lt;/h3&gt;
&lt;h4 id="数据完整性"&gt;数据完整性&lt;/h4&gt;
&lt;p&gt;GFS把Chunk分割为64KB大小的Block，每个Block对应一个32位的Chucksum，用于校验数据的完整性。&lt;/p&gt;
&lt;p&gt;Chuncsum和数据和用户数据分开存储，保存在内存中，并最终通过WAL持久化。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;机器磁盘损坏，重复的数据append等都会导致数据完整性有问题&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="冗余存储"&gt;冗余存储&lt;/h4&gt;
&lt;p&gt;Chunk多副本异架异地存储，单副本丢失不会影响系统可用性。&lt;/p&gt;
&lt;h4 id="一致性"&gt;一致性&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://spongecaptain.cool/images/img_paper/image-20200719211636393.png" alt="image-20210908130412123" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;我暂时没法很好的理解GFS的一致性模型，于是选择将原文奉上：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“ &lt;strong&gt;The state of a file region after a data mutation depends on the type of mutation&lt;/strong&gt;, whether it succeeds or fails, and whether there are concurrent mutations.&lt;/p&gt;
&lt;p&gt;A file region is consistent if all clients will always see the same data, regardless of which replicas they read from. A region is defined after a file data mutation if it is consistent and clients will see what the mutation writes in its entirety.&lt;/p&gt;
&lt;p&gt;When a mutation succeeds without interference from concurrent writers, the affected region is defined (and by implication consistent): all clients will always see what the mutation has written.&lt;/p&gt;
&lt;p&gt;Concurrent successful mutations leave the region undefined but consistent: all clients see the same data, but it may not reflect what any one mutation has written. Typically, it consists of mingled fragments from multiple mutations.&lt;/p&gt;
&lt;p&gt;A failed mutation makes the region inconsistent (hence also undefined): different clients may see different data at different times ”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总之，&lt;strong&gt;GFS无法保证数据强一致&lt;/strong&gt;，它的一致性模型非常宽松。&lt;/p&gt;
&lt;p&gt;Lease机制虽然能够使得并发顺序写入得到合理的操作顺序，但实际的数据Atomic Record Append采用的事At least Once消息模型，确保写入成功，但可能重复写入。随机写也无法保证数据一致。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;一切的系统设计都是需求和业务驱动的，设计中必然涉及对场景、业务、需求、实现等多方面的trade_off。&lt;/p&gt;
&lt;p&gt;分布式系统的可扩展性的重要性要远远高于单机性能。&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://spongecaptain.cool/post/paper/googlefilesystem/"&gt;Google File System-GFS 论文阅读&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tanxinyu.work/gfs-thesis/"&gt;GFS论文阅读&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://nxwz51a5wp.feishu.cn/docs/doccnNYeo3oXj6cWohseo6yB4id"&gt;GFS论文导读&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/gfs-sosp2003.pdf"&gt;GFS Paper&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pdos.csail.mit.edu/6.824/schedule.html"&gt;MIT6.824&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-MapReduce</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-mapreduce/</link><pubDate>Fri, 22 Jan 2021 17:02:44 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-mapreduce/</guid><description>&lt;p&gt;大三上学期课程有点硬核，一直没时间去继续6.824的学习，于是学习进度一直停在了Lab 1。寒假时间稍微充裕了点，于是打算继续推进。之后的每一个论文或者实验都会记录在文章中。&lt;/p&gt;
&lt;p&gt;本文Distributed System学习笔记的第一章。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="论文相关"&gt;论文相关&lt;/h2&gt;
&lt;p&gt;论文最核心的内容是提出的MapReduce分布式计算模型，以及实现&lt;strong&gt;Distributed&lt;/strong&gt; MapReduce System的思路，包括Master数据结构，容错以及一些refinement等内容。&lt;/p&gt;
&lt;h3 id="mapreduce计算模型"&gt;MapReduce计算模型&lt;/h3&gt;
&lt;p&gt;模型接受一系列的键值对作为输入，并输出一系列键值对作为结果。用户通过设计Map和Reduce函数来使用MapReduce System&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Map：接受输入数据，生成一组中间键值对&lt;/li&gt;
&lt;li&gt;Reduce：接受中间键值对作为输入，将所有相同key的数据合并并作为结果输出。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code-block" data-language="cpp"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;cpp&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// key: document name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// value: document contents
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;EmitIntermediate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iterator&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// key: a word
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// values: a list of counts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="nl"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;ParseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="mapreduce执行过程"&gt;MapReduce执行过程&lt;/h3&gt;
&lt;p&gt;Distrubuted MapReduce System采用主从的设计，在MapReduce计算过程中，一般有一个Master，以及若干个Worker。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Master：负责Map以及Reduce任务的创建、分配、调度等&lt;/li&gt;
&lt;li&gt;Worker：负责执行Map以及Reduce任务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/01/12/UK8yJRHc5DzMg3u.png" alt="Screenshot_20210112_125637" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;更详细的描述为:&lt;/p&gt;
&lt;p&gt;1.MapReduce的整个执行过程包含M个Map Task 和R个Reduce Task，分为两个执行阶段Map Phase 和Reduce Phase。&lt;/p&gt;
&lt;p&gt;2.输入的文件被拆分为M个split，计算进入Map Phase阶段，Master分配Map Task给空闲Worker。分配了Task的Worker读取对应的split data，执行Task。直到所有的Map Task都完成，Map Phase结束。利用Partition函数（一般为&lt;code&gt;hash(key) mod R&lt;/code&gt;)得到R组中间键值对，保存在文件中，并将文件路径告知Master，以便Reduce Task的操作。&lt;/p&gt;
&lt;p&gt;3.计算进入Reduce Phase阶段，Master分配Reduce Task，每个Worker读取对应的中间键值对文件，执行Task。所有Reduce执行完成后，计算完成。结果保存到结果文件中。&lt;/p&gt;
&lt;h3 id="mapreduce--容错机制"&gt;MapReduce 容错机制&lt;/h3&gt;
&lt;p&gt;由于 Google MapReduce 很大程度上利用了由 Google File System 提供的分布式原子文件读写操作，所以 MapReduce 集群的容错机制实现相比之下便简洁很多，也主要集中在任务意外中断的恢复上。&lt;/p&gt;
&lt;h4 id="worker容错"&gt;Worker容错&lt;/h4&gt;
&lt;p&gt;在集群中，Master 会周期地向每一个 Worker 发送 Ping 信号。如果某个 Worker 在一段时间内没有响应，Master 就会认为这个 Worker 已经不可用。&lt;/p&gt;
&lt;p&gt;任何分配给该 Worker 的 Map 任务，无论是正在运行还是已经完成，都需要由 Master 重新分配给其他 Worker，因为该 Worker 不可用也意味着存储在该 Worker 本地磁盘上的中间结果也不可用了。Master 也会将这次重试通知给所有 Reducer，没能从原本的 Mapper 上完整获取中间结果的 Reducer 便会开始从新的 Mapper 上获取数据。&lt;/p&gt;
&lt;p&gt;如果有 Reduce 任务分配给该 Worker，Master 则会选取其中尚未完成的 Reduce 任务分配给其他 Worker。鉴于 Google MapReduce 的结果是存储在 Google File System 上的，已完成的 Reduce 任务的结果的可用性由 Google File System 提供，因此 MapReduce Master 只需要处理未完成的 Reduce 任务即可。&lt;/p&gt;
&lt;p&gt;如果集群中有某个 Worker 花了特别长的时间来完成最后的几个 Map 或 Reduce 任务，整个 MapReduce 计算任务的耗时就会因此被拖长，这样的 Worker 也就成了落后者（Straggler）。&lt;/p&gt;
&lt;p&gt;MapReduce 在整个计算完成到一定程度时就会将剩余的任务进行备份，即同时将其分配给其他空闲 Worker 来执行，并在其中一个 Worker 完成后将该任务视作已完成。&lt;/p&gt;
&lt;h4 id="master容错"&gt;Master容错&lt;/h4&gt;
&lt;p&gt;整个 MapReduce 集群中只会有一个 Master 结点，因此 Master 失效的情况并不多见。&lt;/p&gt;
&lt;p&gt;Master 结点在运行时会周期性地将集群的当前状态作为保存点（Checkpoint）写入到磁盘中。Master 进程终止后，重新启动的 Master 进程即可利用存储在磁盘中的数据恢复到上一次保存点的状态。&lt;/p&gt;
&lt;h3 id="refinement"&gt;Refinement&lt;/h3&gt;
&lt;h4 id="partition-function"&gt;Partition Function&lt;/h4&gt;
&lt;p&gt;于Map Phase阶段使用，将中间键值对按照规则分配到R个文件中保存&lt;/p&gt;
&lt;h4 id="combiner"&gt;Combiner&lt;/h4&gt;
&lt;p&gt;在某些情形下，用户所定义的 Map 任务可能会产生大量重复的中间结果键，Combiner 函数以对中间结果进行局部合并，减少 Mapper 和 Reducer 间需要传输的数据量。&lt;/p&gt;
&lt;h2 id="实验相关"&gt;实验相关&lt;/h2&gt;
&lt;p&gt;实验内容主要是设计实现Master和Worker，补全Simple MapReduce System的主要功能。&lt;/p&gt;
&lt;p&gt;实验中通过Rpc调用实现单Master以及多Worker的模型，通过Go Plugin运行Map和Reduce函数组成的不同应用。&lt;/p&gt;
&lt;h3 id="masterworker功能"&gt;Master&amp;amp;Worker功能&lt;/h3&gt;
&lt;h4 id="master"&gt;Master&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;任务的创建，调度等&lt;/li&gt;
&lt;li&gt;Worker的注册，为其分配Task&lt;/li&gt;
&lt;li&gt;接受Worker当前的运行状态&lt;/li&gt;
&lt;li&gt;监听Task运行状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="worker"&gt;worker&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在Master中注册&lt;/li&gt;
&lt;li&gt;获取任务并处理&lt;/li&gt;
&lt;li&gt;报告运行状态&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注：Master通过Rpc提供相应功能给Worker调用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="主要数据结构"&gt;主要数据结构&lt;/h3&gt;
&lt;p&gt;数据结构的设计是主要的工作，良好的设计结构有助于功能的实现。此处之展示数据结构相关代码，具体的功能实现见&lt;a href="https://github.com/noneback/Toys/tree/master/6.824-Lab1-MapReduce"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="master-1"&gt;Master&lt;/h4&gt;
&lt;div class="code-block" data-language="go"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;go&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Master&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Your definitions here.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nReduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;taskQueue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tasksContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;TaskContext&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mutex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PhaseKind&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workerID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id="worker-1"&gt;Worker&lt;/h4&gt;
&lt;div class="code-block" data-language="go"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;go&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mapf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;KeyValue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reducef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nReduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id="task--taskcontext"&gt;Task &amp;amp; TaskContext&lt;/h4&gt;
&lt;div class="code-block" data-language="go"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;go&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Phase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PhaseKind&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TaskContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ContextState&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workerID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id="rpc-args--reply"&gt;Rpc Args &amp;amp; Reply&lt;/h4&gt;
&lt;div class="code-block" data-language="go"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;go&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RegTaskArgs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WorkerID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RegTaskReply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HasT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReportTaskArgs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WorkerID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TaskID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ContextState&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReportTaskReply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RegWorkerArgs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RegWorkerReply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NReduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id="constant--type"&gt;Constant &amp;amp; Type&lt;/h4&gt;
&lt;div class="code-block" data-language="go"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;go&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RUNNING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ContextState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;iota&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FAILED&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;READY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IDEL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;COMPLETE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MAX_PROCESSING_TIME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SCHEDULE_INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MAP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PhaseKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;iota&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;REDUCE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ContextState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PhaseKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="运行与测试"&gt;运行与测试&lt;/h3&gt;
&lt;h4 id="运行"&gt;运行&lt;/h4&gt;
&lt;div class="code-block" data-language="bash"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;bash&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# In main directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ./src/main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Master&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go run ./mrmaster.go pg*.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Worker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go build -buildmode&lt;span class="o"&gt;=&lt;/span&gt;plugin ../mrapps/wc.go &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; go run ./mrworker.go ./wc.so &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id="测试"&gt;测试&lt;/h4&gt;
&lt;div class="code-block" data-language="bash"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;bash&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ./src/main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sh ./test-mr.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="优化"&gt;优化&lt;/h2&gt;
&lt;p&gt;这些优化是在我完成实验之后，回顾自己代码时想到的可以优化的一些设计。&lt;/p&gt;
&lt;h3 id="热点问题"&gt;热点问题&lt;/h3&gt;
&lt;p&gt;这里的热点问题是，可能会有一个热点数据频繁的出现在数据集中。Map阶段的中间结果值K-V形式的，这样会导致在shuffle步骤的时候，一个Key频繁的出现，进而导致有个别机器的磁盘IO和网络IO被大量的占用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个问题的本质在于，MapReduce中的Shuffle在&lt;strong&gt;设计&lt;/strong&gt;上是强依赖于数据的。&lt;/p&gt;
&lt;p&gt;它的&lt;strong&gt;设计目的&lt;/strong&gt;就是为了聚合中间结果数据以便Reduce阶段能够更好的进行处理。基于此，在数据极度分布不均的时候，自然会有热点问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;实际上，问题本质是，大量的Key在Hash处理后被分配到以一个磁盘文件中，作为后续Reduce的输入。&lt;/p&gt;
&lt;p&gt;同一个Key的Hash值理当是相同的，所以问题可以变形为：&lt;em&gt;如何让相同的Key的Hash分桶到不一样机器中？&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;目前我想到的可行方式就是在Shuffle的Hash计算中，为Key添加随机salt，使得Hash的值不相同，减少哈希分桶到同一个机器的概率，进而解决热点问题。&lt;/p&gt;
&lt;h3 id="容错问题"&gt;容错问题&lt;/h3&gt;
&lt;p&gt;对于容错问题其实论文中已经有了一些解决方案。这个问题的场景是：Worker节点的机器突然Crash，并在重启之后重新连接。Master观测到Worker的Crash并将其任务重新分配给其他节点执行，这是Worker节点重新连接，之前的执行还在继续，双方都执行，可能导致生成两份结果文件。&lt;/p&gt;
&lt;p&gt;这里的潜在问题是，这两份文件可能会导致结果出现错误。同时，重新连接的Worker继续执行原有的任务，浪费CPU，IO资源。&lt;/p&gt;
&lt;p&gt;基于此，我们需要标明生成结果文件的新旧，只有最新的文件才能够被作为结果统计，这样就解决了文件冲突；同时，为Worker节点添加一个Rpc接口，使得其重新连接的时候，Master可以调用以清除原有任务。&lt;/p&gt;
&lt;h3 id="长尾问题"&gt;长尾问题&lt;/h3&gt;
&lt;p&gt;长尾问题，就是指某个Task执行时间长，导致MapReduce无法迅速完成。其实本质上就是热点问题和Worker的Crash处理问题，可以参考上述博客。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://pdos.csail.mit.edu/6.824/index.html"&gt;MIT6.824 Distributed System&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-mr.html"&gt;Lab Official Site&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://static.googleusercontent.com/media/research.google.com/zh-CN//archive/mapreduce-osdi04.pdf"&gt;MapReduce: Simplified Data Processing on Large Clusters&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://zhuanlan.zhihu.com/p/34849261"&gt;Google MapReduce 论文详解&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;</description></item></channel></rss>