<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>MIT6.824 on NoneBack</title><link>https://noneback.github.io/zh/tags/mit6.824/</link><description>Recent content in MIT6.824 on NoneBack created by</description><generator>Hugo -- gohugo.io</generator><language>zh</language><copyright>@NoneBack All rights reserved</copyright><lastBuildDate>Tue, 01 Aug 2023 16:11:54 +0800</lastBuildDate><atom:link href="https://noneback.github.io/zh/tags/mit6.824/index.xml" rel="self" type="application/rss+xml"/><item><title>MIT6.824 AuroraDB</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-auroradb/</link><pubDate>Tue, 01 Aug 2023 16:11:54 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-auroradb/</guid><description>&lt;p&gt;这篇文章介绍了AWS的数据库产品Aurora的设计考虑，包括存算分离、一写多读、基于Quorum的NRW一致性协议等。同时，文章也提到了PolarDB参考Aurora进行设计，但在网络瓶颈和系统调用方面有所不同。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Aurora是AWS提供的一种数据库产品，主要面向OLTP的业务场景。&lt;/p&gt;
&lt;p&gt;设计上，我觉得有这些值得参考的地方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Aurora设计的前提是，在数据库上云之后，得益于云基础设施的发展，数据库最大的瓶颈从计算和存储变成了网络， 这是AWS在设计Aurora的时候一个很重要的前提。基于此前提，Aurora重提Log is Database的论调，只将RedoLog下推至存储层。&lt;/li&gt;
&lt;li&gt;存算分离。数据库存储层对接分布式存储底座，通过存储底座提供良好的可靠性和安全性保证。计算和存储层可以独立拓展。同时，存储底座对上层提供的单一数据视图，使得一些核心功能和运维操作效率得到很好的提升（比如备份，数据恢复，HA等）&lt;/li&gt;
&lt;li&gt;一些有意思的可靠性保证。比如基于Quorum的NRW一致性协议，存储节点读写都需要多数派的投票。保证双AZ级别的容错；用分片存储减少故障处理时间，以此提升SLA。多数读只发生在数据库恢复的时候，此时数据库需要恢复当前的状态。&lt;/li&gt;
&lt;li&gt;一写多读。不同于ShareNothing架构的NewSQL产品，Aurora只提供了单个写节点。数据一致性保证也因此变得简单，因为单写节点可以通过RedoLog LSN作为逻辑时钟，以此维护数据更新操作的偏序关系，只需要把RedoLog下推至所有节点，并基于此顺序对这些操作Apply就可以保证数据的一致性。&lt;/li&gt;
&lt;li&gt;事务的实现。由于存储底座对上层提供的单一文件视图，所以对与Aurora来说，其事务的实现几乎与单机事务算法相同，并能提供相同的事务语义。NewSQL的事务一般是基于2PC的分布式事务实现。&lt;/li&gt;
&lt;li&gt;后台加速前台处理。类似LevelDB的思路，尽可能将存储节点的一些操作异步化（比如日志Apply），提升前台用户感知性能。这些异步的操作通过维护各种xxLSN来记录当前节点的后台处理进度，比如VLSN，commit-LSN等等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/20230412094745.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/20230412094745.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/20230412094928.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/20230412094928.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/20230412094941.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/20230412094941.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;有趣的是，PolarDB虽然是参考Aurora进行的设计，但它的架构设计认为网络并非瓶颈，而是经过OS的各种系统调用拖慢了整体速度。在彼时阿里云存储底座并不稳定的条件下，所以才有了它架构中的PolarStore，用各种硬件以及FUSE等存储技术越过或者优化系统调用，而如今盘古在稳定性和性能上都做的很不错的情况下，弱化PolarStore这个组件也成为了正常的选择。我认为说的不无道理。&lt;/p&gt;
&lt;p&gt;另外，为什么他们选择用NWR而不是用Raft之类的一致性协议？目前看上去，NWR在网络上，一次请求的网络比Raft少一轮，可能是这个原因&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/20230412094918.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/20230412094918.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;h1 id="参考"&gt;参考&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://zhuanlan.zhihu.com/p/319806107"&gt;https://zhuanlan.zhihu.com/p/319806107&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://nil.csail.mit.edu/6.824/2020/notes/l-aurora.txt"&gt;http://nil.csail.mit.edu/6.824/2020/notes/l-aurora.txt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://keys961.github.io/2020/05/05/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB-Aurora/"&gt;论文阅读-Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Database - keys961 | keys961 Blog&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-ChainReplication</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-chainreplication/</link><pubDate>Wed, 08 Feb 2023 23:05:57 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-chainreplication/</guid><description>&lt;p&gt;只是简单写写，有一些具体一点的设计建议去读一下原文。&lt;/p&gt;
&lt;h1 id="简介"&gt;简介&lt;/h1&gt;
&lt;p&gt;简单来讲，CR论文介绍了一种用于存储服务的满足线性一致性的复制状态机算法。它通过链式复制来提高算法的吞吐量，通过多副本来保证服务的可用性。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/20230215135829.png" alt="image-20230208232032286" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;算法的设计很简单巧妙。算法通过链式复制将复制的吞吐均分到所有的CR节点上，每个节点只负责对后续节点的复制。写入请求从头部节向后传播，查询请求交由尾部节点响应。&lt;/p&gt;
&lt;p&gt;为了维护Chain上节点之前的前后关系，CR还引入了一个Master服务，用于管理节点之间的关系，以及处理Node Failure的情况。&lt;/p&gt;
&lt;h2 id="故障处理"&gt;故障处理&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Head Fail：头节点Pending or 没处理完 的请求将被丢失，不影响线性一致性，之后将第二个节点设置为头结点&lt;/li&gt;
&lt;li&gt;Tail Fail：倒数第二个节点将成为尾节点，此时原来Tail Pending的请求将被提交&lt;/li&gt;
&lt;li&gt;Middle Fail：中间节点故障的处理，类比链表的操作。Node_pre 将指向 Node_next，此时我们需要保证故障Node传递的请求被完整的传递下去。每个CR节点会维护一个SendReqList，记录已传递给后续节点的请求，由于请求是从头到尾传播，所以Node_pre只需要Node_next所缺失的数据即可。当Tail接收到请求时，标识数据被提交，此时会从尾至头传递Ack(req)信息，经过的节点都会把req从SendReqList从去除。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="优缺点"&gt;优缺点&lt;/h2&gt;
&lt;p&gt;最大的优点是，可以单节点提高吞吐量，节点的负载比较均衡，同时相对易于实现。整体设计很简洁有效。&lt;/p&gt;
&lt;p&gt;但很明显也会有以下缺点。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果传播链条上有一个节点处理慢，将会拖慢整个处理流程。&lt;/li&gt;
&lt;li&gt;除了首尾两个节点，其他节点的数据基本只作为副本存在，而无法提供服务。当然CRAQ里有让中间节点提供只读服务的方法，类似Raft的Read Index，暂不细说。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="参考"&gt;参考&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://tanxinyu.work/chain-replication-thesis/"&gt;Chain Replication 论文阅读&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://nil.csail.mit.edu/6.824/2021/papers/cr-osdi04.pdf"&gt;CR Paper&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-ZooKeeper</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-zookeeper/</link><pubDate>Tue, 03 Jan 2023 23:49:41 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-zookeeper/</guid><description>&lt;p&gt;本文主要讲了ZooKeeper系统在设计和实践上的考量，如wait-free和lock，一致性的选择，系统提供的API以及特定语义上的抉择，这样的trade-off是本文的最大启发。&lt;/p&gt;
&lt;h2 id="定位"&gt;定位&lt;/h2&gt;
&lt;p&gt;wait-free,high-performance 的协调分布式应用的系统。通过提供协调原语（特定语义的API与数据模型）支持分布式应用的协调需求。&lt;/p&gt;
&lt;h2 id="设计"&gt;设计&lt;/h2&gt;
&lt;h3 id="关键词"&gt;关键词&lt;/h3&gt;
&lt;p&gt;ZK定位中的关键词有两个：&lt;strong&gt;高性能，分布式应用协调服务&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;ZK的高性能通过WaitFree设计、多副本本地读、Watch机制实现。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WaitFree应该是将请求异步处理来实现的，这样异步处理可能会导致请求重排序，导致状态机和现实的时序不同，所以ZK提供了FIFO Client Order 顺序保证。同时，这样的异步处理有益于数据的batch pipeline处理，进一步提升性能。&lt;/li&gt;
&lt;li&gt;Watch机制，当znode变化是通知Client更新，避免Client操作本地缓存的开销。&lt;/li&gt;
&lt;li&gt;多副本本地读，ZK使用ZAB协议实现数据共识，保证写操作满足linearizability。读请求副本本地读，不走ZAB共识协议，但读请求只满足Serializaility，可能会读到之前的结果，但提升了性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分布式应用协调服务指的是，ZK提供的数据模型以及API语义，分布式应用可以自由使用来满足诸如Group Membership，Distributed Lock等协调需求。&lt;/p&gt;
&lt;h3 id="数据模型与api"&gt;数据模型与API&lt;/h3&gt;
&lt;p&gt;ZK为使用者提供znode数据节点的抽象，数据节点通过分层的命名空间组织。ZK提供了Regular + Ephemeral两种znode的节点的创建，每个节点都存储数据，并通过标准的UNIX文件系统路径访问。&lt;/p&gt;
&lt;p&gt;实际上，znodes 不是为通用数据存储设计的。 相反，znodes 映射到客户端应用程序的抽象，通常与用于协调的&lt;strong&gt;元数据&lt;/strong&gt;相对应。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;也就是说，使用ZK进行协调时，利用好znode关联的元数据，而不是只将znode当做数据存储。比如，znode 将元数据与时间戳(timestamp)和版本计数器( version counter )关联，客户端可以跟踪对 znode 的更改并根据 znode 的版本执行条件更新。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这套数据模型本质上是一个简化API的文件系统，只支持完整数据的读写。使用者将在ZK提供的语义下实现分布式应用的协调。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Regular 和 Ephemeral 的区别在于Ephemeral可以在Session结束时自动删除。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/c9c4c039-a334-4c00-946c-743e6ab984d9/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;amp;X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;amp;X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230103%2Fus-west-2%2Fs3%2Faws4_request&amp;amp;X-Amz-Date=20230103T155342Z&amp;amp;X-Amz-Expires=86400&amp;amp;X-Amz-Signature=7b1041157b56fe404023a2303762de9bb599c57d116bc10b9f46e1733f67bbc2&amp;amp;X-Amz-SignedHeaders=host&amp;amp;response-content-disposition=filename%3D%22Untitled.png%22&amp;amp;x-id=GetObject" alt="img" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;Client通过API与ZK交互，ZK通过Session管理Clinet连接，在Session中Clinet可以观测到反应其操作的状态变化。&lt;/p&gt;
&lt;h2 id="cap"&gt;CAP&lt;/h2&gt;
&lt;p&gt;ZK保证CP，比如在选举leader时，会停止服务，直到选举成功之后才会再次对外提供服务。&lt;/p&gt;
&lt;h2 id="实现"&gt;实现&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/cb5e3866-1ce2-4897-aa47-c486c10aba12/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;amp;X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;amp;X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230103%2Fus-west-2%2Fs3%2Faws4_request&amp;amp;X-Amz-Date=20230103T155414Z&amp;amp;X-Amz-Expires=86400&amp;amp;X-Amz-Signature=35715be3617f7544fc7fcc05705f99a32d46e0ca9c31af2d51f383148f316f32&amp;amp;X-Amz-SignedHeaders=host&amp;amp;response-content-disposition=filename%3D%22Untitled.png%22&amp;amp;x-id=GetObject" alt="img" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;ZK使用多副本实现高可用。&lt;/p&gt;
&lt;p&gt;简单来说，ZK上层使用ZAB协议处理写请求，保证多副本更新的线性一致性，本地处理读请求，读请求保证顺序一致性。下层数据状态机保存到ZK集群机器上的Replicated Database（内存）+ WAL上，并定期snapshot。整个内存数据库通过 Fuzzy Snapshot + WAL Replay的方式保证单机Crash Safe以及重启恢复的速度。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fuzzy Snapshot 的优势在于不阻塞在线请求。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="与client的交互"&gt;与Client的交互&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;更新操作会通知并清除相关znode的Watch。&lt;/li&gt;
&lt;li&gt;读请求本地进行，通过zxid定义与写请求的偏序关系，只保证顺序一致性，可能会Read Stale。ZK提供了sync操作，通过 sync + read 一定程度上解决了这个问题。&lt;/li&gt;
&lt;li&gt;当Client连接新ZK Server时，会对比两者的最大zxid，落后的ZK Server将不会为Client建立Session。&lt;/li&gt;
&lt;li&gt;Client通过心跳维持Session，Server对请求进行幂等处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://pdos.csail.mit.edu/6.824/papers/zookeeper.pdf"&gt;ZooKeeper Paper&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pdos.csail.mit.edu/6.824/papers/zookeeper-faq.txt"&gt;MIT6.824-ZooKeeper FAQ&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-RaftKV</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-raftkv/</link><pubDate>Fri, 15 Apr 2022 10:49:57 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-raftkv/</guid><description>&lt;p&gt;之前因为想试一试GSOC，所以看了看Casbin-Mesh的代码，这是基于Raft的一个分布式Casbin应用。这个MIT6.824里的RaftKV很类似，所以正好借此机会写下这篇博客。&lt;/p&gt;
&lt;h2 id="实验相关"&gt;实验相关&lt;/h2&gt;
&lt;p&gt;Lab03的内容是在Raft基础上构建一个分布式KV服务。我们需要实现此服务的Server和Client。&lt;/p&gt;
&lt;p&gt;RaftKV的结构和各个模块的交互如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/04/29/xuQMp28PRH7rheb.png" alt="image-20220429211429808" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;相比于上个实验难度低了不少，实现上可以参考这篇大佬的&lt;a href="https://github.com/OneSizeFitsQuorum/MIT6.824-2021/blob/master/docs/lab3.md"&gt;实现&lt;/a&gt;，我就不多写了。&lt;/p&gt;
&lt;h2 id="raft-相关"&gt;Raft 相关&lt;/h2&gt;
&lt;p&gt;接下来说说Raft中有关客户端交互有关的内容。&lt;/p&gt;
&lt;h3 id="路由与线性化语义"&gt;路由与线性化语义&lt;/h3&gt;
&lt;p&gt;想要在Raft之上构建允许客户端访问的服务，首先要解决&lt;strong&gt;路由&lt;/strong&gt;和&lt;strong&gt;线性化语义&lt;/strong&gt;的问题。&lt;/p&gt;
&lt;h4 id="路由"&gt;路由&lt;/h4&gt;
&lt;p&gt;Raft是一个&lt;strong&gt;Strong Leader&lt;/strong&gt;的共识算法，读写请求一般都需要通过Leader执行。客户端反问Raft集群时，一般会随机访问集群中一个节点，如果它不是Leader, 那么它会将保存的leader信息返回给客户端，客户端会将请求重定向到Leader节点重试。&lt;/p&gt;
&lt;h4 id="线性化语义"&gt;线性化语义&lt;/h4&gt;
&lt;p&gt;此为，目前的Raft只支持&lt;strong&gt;At Least Once&lt;/strong&gt;的语义，对于客户端的一次请求，Raft状态机可能会应用多次命令，而这样的语义特别不适用于基于共识的系统。&lt;/p&gt;
&lt;p&gt;为了实现线性化语义，很显然，我们需要让发出的请求实现幂等。&lt;/p&gt;
&lt;p&gt;一个基本的思路是客户端给每个请求分配UID, 而服务端利用这个&lt;code&gt;UID&lt;/code&gt;维护一个Session,对成功请求的Response进行缓存。当有重复的请求到达服务端时，它可以直接利用Session缓存的Response相应，这样就实现了请求幂等。&lt;/p&gt;
&lt;p&gt;当然这带来了Session管理的问题，但这个并非本文重点。&lt;/p&gt;
&lt;h3 id="只读优化"&gt;只读优化&lt;/h3&gt;
&lt;p&gt;解决了上面两个问题之后，我们得到了一个可用的基于Raft的服务。&lt;/p&gt;
&lt;p&gt;但我们会发现，无论是读或是写请求，我们的应用都需要经过Leader 发起一次&lt;code&gt;AppendEntries&lt;/code&gt;的通信，在收到了Quorum成功的ACK，以及附加的落盘操作，在Log Committed再之后才能将结果返回给客户端。&lt;/p&gt;
&lt;p&gt;写操作会改变数据状态机，所以对于写请求这些是必要的。但读操作并不会改变状态机，我们可以对只读请求进行一些优化，让只读请求绕过Raft日志，以便减少同步写操作带来的磁盘IO开销。&lt;/p&gt;
&lt;p&gt;问题在于，如果没有其他的措施，绕过Raft日志的只读查询结果可能是过时的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;比如，旧集群Leader和一个选出新Leader的集群发生了分区，此时客户端在旧Leader上的查询结果可能会过时。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Raft论文中提到了&lt;strong&gt;Read Index&lt;/strong&gt;和&lt;strong&gt;Lease Read&lt;/strong&gt;两种方法来绕过Raft日志，优化只读请求。&lt;/p&gt;
&lt;h4 id="read-index"&gt;Read Index&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Read Index&lt;/strong&gt;方案需要解决几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旧任期遗留的已提交的日志&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;如old leader提交Log后，没来的及发送心跳就崩溃了。此时其他节点选中为新Leader，但根据Raft论文，新leader并不会主动提交旧leader时的日志。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，我们需要在新Leader当选后提交一个no-op日志，将旧Log提交。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;CommitIndex和AppliedIndex的间隔&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;引入&lt;code&gt;readIndex&lt;/code&gt;变量，领导者将当前&lt;code&gt;commitIndex&lt;/code&gt;保存在局部变量&lt;code&gt;readIndx&lt;/code&gt;，以此作为查询时AppliedIndex的界限，当只读请求到来时，需要先将Log应用到&lt;code&gt;readIndex&lt;/code&gt;记录的位置，之后Leader才能查询状态机，提供读服务。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;在提供只读服务时候保证Leader不发生切换&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;为了解决这个问题，我们在收到读请求后，Leader会先进行心跳，并需要收到Quorum数量的Ack，保证在此时不存在其他任期更大的Leader，保证&lt;code&gt;readIndex&lt;/code&gt;是集群中的最大提交索引。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;具体的流程以及Batch和Follower Read的优化可以参考Raft作者的博士论文，在此不再赘述。&lt;/p&gt;
&lt;h4 id="lease-read"&gt;Lease Read&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Read Index&lt;/strong&gt;的方案其实只优化了磁盘IO的开销，它依旧需要进行一轮集群的网络通信。但实际上，这部分开销也是可以进行优化的，于是就有了&lt;strong&gt;Lease Read&lt;/strong&gt;的方案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lease Read&lt;/strong&gt;方案的&lt;strong&gt;核心思路&lt;/strong&gt;是利用一次Leader Election至少需要经过一轮ElectionTimeout时间。在此期间，系统不会进行重新选举。这样就避免了提供只读服务时Leader切换的问题。所以我们可以利用时钟优化网络IO。&lt;/p&gt;
&lt;h5 id="实现"&gt;实现&lt;/h5&gt;
&lt;p&gt;在实现上，为了让时钟代替网络信息交互，我们需要额外提供一种租约机制。一旦Quorum数量的集群认可了领导者的&lt;code&gt;Heartbeat&lt;/code&gt;，Leader可以认为在&lt;code&gt;ElectionTimeout&lt;/code&gt;期间没有其他人能成为Leader，它可以相应的延长其租约。但Leader持有租约时，它可以直接服务只读查询而不需要额外的网络通信。&lt;/p&gt;
&lt;p&gt;但其实服务器中间可能会存在&lt;strong&gt;时钟偏移&lt;/strong&gt;，这样Follower就无法保证在Leader持有租约时不会超时。这就引出了&lt;code&gt;Lease Read&lt;/code&gt;的关键设计：&lt;strong&gt;用什么策略延长租期呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;论文中，假设$ClokcDrift$是有界的，每次心跳成功更新租约时，租约延长到$start + \frac{ElectionTimeout}{ClockDriftBound}$ 。&lt;/p&gt;
&lt;p&gt;$ClokcDriftBound$代表了集群时钟漂移的界限，但是这个界限的发现和维护十分困难，因为导致时钟漂移的原因有很多，并且具有实时性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如GC，虚拟机调度，云服务机器扩缩容等&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;实践上，一般会牺牲一定的安全性来换取&lt;code&gt;LeaseRead&lt;/code&gt;的性能。一般使用$StatrTime +ElectionTimeout - \Delta{t}$来延长租期。$\Delta{t}$是一个正数，这就使得每次延长租约的时间小于&lt;code&gt;ElectionTimeout&lt;/code&gt;，在网络IO开销和安全性之间Trade Off。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;Raft构建服务时，首先需要设计好访问服务以及路由和幂等机制。&lt;/p&gt;
&lt;p&gt;对于只读操作，优化手段主要有两种，&lt;strong&gt;Read Index&lt;/strong&gt; 和 &lt;strong&gt;Lease Read&lt;/strong&gt;。其中前者优化了读操作时的磁盘IO，后者利用时钟优化了网络IO。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/OneSizeFitsQuorum/MIT6.824-2021/blob/master/docs/lab3.md"&gt;Implimetation doc&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/raft-thesis-zh_cn"&gt;Consensus: Bridging Theory and Practice - zh&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pingcap.com/zh/blog/lease-read"&gt;Tikv lease-read&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>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>