<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Distributed System on NoneBack</title><link>https://noneback.github.io/zh/tags/distributed-system/</link><description>Recent content in Distributed System on NoneBack created by</description><generator>Hugo -- gohugo.io</generator><language>zh</language><copyright>@NoneBack All rights reserved</copyright><lastBuildDate>Thu, 28 Sep 2023 10:43:23 +0800</lastBuildDate><atom:link href="https://noneback.github.io/zh/tags/distributed-system/index.xml" rel="self" type="application/rss+xml"/><item><title>Percolator: Large-scale Incremental Processing Using Distributed Transactions and Notifications</title><link>https://noneback.github.io/zh/blog/zh/percolator/</link><pubDate>Thu, 28 Sep 2023 10:43:23 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/percolator/</guid><description>&lt;p&gt;好久没学习了，学点想学的。本次内容是分布式事务Percolator, 不翻译论文，没有算法细节，只记录自己的理解。&lt;/p&gt;
&lt;h2 id="percolator-和-2pc"&gt;Percolator 和 2PC&lt;/h2&gt;
&lt;h3 id="2pc"&gt;2PC&lt;/h3&gt;
&lt;p&gt;两阶段提交协议中包含两种角色，&lt;strong&gt;Coordinator和Participant&lt;/strong&gt;。协调者负责整个协议的推进，使得多个参与者最终达到一致的决议。参与者响应协调者的请求，根据协调者的请求完成 prepare 操作及 commit/abort 操作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2PC协议保证事务的原子性(ACD)&lt;/strong&gt;，并&lt;strong&gt;未对隔离性(I)做任何实现&lt;/strong&gt;，依赖单机事务(ACD)。Coordinator，显然是关键路径，可能成为单点瓶颈，或者宕机问题阻塞事务流程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="code-block" data-language="text"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;text&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; 协调者 参与者
QUERY TO COMMIT
--------------------------------&amp;gt;
VOTE YES/NO prepare*/abort*
&amp;lt;-------------------------------
commit*/abort* COMMIT/ROLLBACK
--------------------------------&amp;gt;
ACKNOWLEDGMENT commit*/abort*
&amp;lt;--------------------------------
end&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="percolator"&gt;Percolator&lt;/h3&gt;
&lt;p&gt;本身其实算一种经过优化的二阶段提交的实现，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对锁的使用进行优化，引入Primary-Secondary二级锁，去除了对&lt;strong&gt;Coordinator&lt;/strong&gt;的依赖。&lt;/li&gt;
&lt;li&gt;提供了完整的ACID事务语义实现，并且支持MVCC（依赖时间戳服务）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="percolator协议细节"&gt;Percolator协议细节&lt;/h2&gt;
&lt;p&gt;Percolator系统主要有三部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Client，发起事务的客户端，Client 是整个协议的控制中心，是两阶段提交的协调者（Coordinator）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TO，Time Observer，分配时间戳，提供全局唯一且递增的时间戳，实现MVCC&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bigtable，提供单机单行事务，数据存储在Bigtable中，包含数据本身以及附带的一些属性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;lock + write + data&lt;/code&gt;: for transaction，lock表示cell被事务持有，write表示数据的可见性&lt;/p&gt;
&lt;p&gt;&lt;code&gt;notify + ack&lt;/code&gt;: for watcher or notifier&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/20230927163910.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/20230927163910.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从外部看，Percolator以SDK的提供给业务方使用。提供事务以及R/W。模型类似于 &lt;code&gt;Begin Txn → Sets of RW Operations → Commit or About or Rollback&lt;/code&gt;其中Bigtable最为持久化组件屏蔽了底层Tablet Server Data Sharding的细节，事务中的每一个写操作或者读后写（统称为Write）操作都视为分布式事务的参与者，而这些操作最后可能会分派到多个Tablet Server节点上。&lt;/p&gt;
&lt;h3 id="算法流程"&gt;算法流程&lt;/h3&gt;
&lt;p&gt;一个事务的所有 Write 在提交之前都会先缓存在 Client，然后在提交阶段一次性写入；Percolator 的事务提交是标准的两阶段提交，分为 Prewrite 和 Commit 。&lt;/p&gt;
&lt;h4 id="prewrite"&gt;Prewrite&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;从TO获取一个时间戳，作为事务的开始时间。&lt;/li&gt;
&lt;li&gt;给数据上锁，标记数据被当前事务占有。上锁失败则表示数据被其他事物占有，当前事物失败。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;上锁过程利用了Primary-Secondary机制，选择一个 Write 作为 primary，其它 Write 则是 secondary。Secondary的锁记录指向Primary。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/202309271613141.png" alt="https://raw.githubusercontent.com/noneback/images/picgo/202309271613141.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;显然，Prewrite阶段的数据对其他事务均不可见。&lt;/p&gt;
&lt;h4 id="commit"&gt;Commit&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;尝试对Prewrite中的数据进行Commit。Commit的时候先对Primary记录进行Commit，Primary记录的提交时间将作为整个事务的提交时间。首先对记录的锁记录进行检测，如果锁不存在，则表示Prewrite阶段的锁被其他事物清理，则事物执行失败。如果存在，则写记录中的write列，表示数据对系统可见。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;异步网络之中，单节点故障，网络延迟很常见。算法需要在发现这些故障的时候，清理掉这些锁记录，避免死锁。所以，在Commit阶段，如果锁不存在，则表示事务的参与者发生了问题当前事务需要被清理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;提交成功之后对锁记录进行清理。显然，锁清理也是可以异步的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些设计使得算法去除了中心化&lt;strong&gt;Coordinator&lt;/strong&gt;的&lt;strong&gt;&lt;strong&gt;依赖&lt;/strong&gt;&lt;/strong&gt;。因为过去需要依赖这个中心服务来维护事务各个参与者的信息。而在本算法中，利用Primary-Secondary二级锁以及Write列就可以实现。Write列表示数据对外的可见性以及数据版本链条。Lock列表示数据被事务持有。Primary-Secondary记录了参与者的逻辑从属关系。这样的设计使得Primary记录的提交变成了整个事务的提交点。一但Primary被提交，所有的Secondary记录可以通过检查对应Primary记录的Write列来进行异步提交。&lt;/p&gt;
&lt;h3 id="snapshot-isolation"&gt;Snapshot Isolation&lt;/h3&gt;
&lt;p&gt;两阶段提交解决的是事务的原子性。在此基础上，Percolator还提供了&lt;strong&gt;&lt;strong&gt;Snapshot Isolation&lt;/strong&gt;&lt;/strong&gt;的隔离性。简而言之，Snapshot Isolation要求提交的事务不能导致数据冲突，事务的读操作满足Snapshot Read。利用事务开始时间以及Primary记录的提交时间，可以维护一个事务之间的全序关系，这些问题自然就可以解决了。&lt;/p&gt;
&lt;h3 id="异步网络的死锁问题"&gt;异步网络的死锁问题&lt;/h3&gt;
&lt;p&gt;之前提到，异步网络之中，单节点故障，网络延迟很常见。算法需要在发现这些故障的时候，清理掉这些锁记录，避免死锁。故障检测的策略可以很简单，比如超时，故障会导致当前事务失败；节点故障又恢复正常，当时的事务已经失败，则此时需要对节点上相关锁记录进行清理。锁的清理可以异步化，比如在Prewrite上锁时，当发现记录Lock列非空，则去检测其Primary锁是否为空，Primary非空，则表示这个事务为完成，可以清理；为空则表示事务提交，则去找到事务提交时间，并把数据提交，再清理锁记录（RollForward）。&lt;/p&gt;
&lt;h3 id="通知机制"&gt;通知机制&lt;/h3&gt;
&lt;p&gt;通知机制对于异步系统的状态观测和联动很重要。但不是本文重点。&lt;/p&gt;
&lt;h2 id="percolator在tidb上的使用"&gt;Percolator在TiDB上的使用&lt;/h2&gt;
&lt;p&gt;基于我们上文的分析，我们可以得出这样的结论：Percolator是一种优化过的2PC分布式事务实现，实现的基础是支持单机事务的存储引擎。&lt;/p&gt;
&lt;p&gt;让我们来简单看看TiDB的的基于Percolator算法实现分布式事务。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://download.pingcap.com/images/docs-cn/tidb-architecture-v6.png" alt="https://download.pingcap.com/images/docs-cn/tidb-architecture-v6.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;TiDB和TiKV架构如上。TiDB关系表里的数据最终都被映射到了TiKV中的KV中。TiKV是一个基于Raft+RocksDB的分布式KV。RocksDB是一个支持事务操作的KV。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://download.pingcap.com/images/docs/tikv-rocksdb.png" alt="https://download.pingcap.com/images/docs/tikv-rocksdb.png" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;所以TiDB的事务的执行路径可以是这样：对关系表的事务操作转为对一组KV的事务操作，再基于Percolator去执行，以此实现关系表的事务操作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当然不可能提供和单机TP数据库一样的事务语义和性能保证。但Share Nothing架构也有自己的优点，所以这个也许并不重要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://zhuanlan.zhihu.com/p/22594180"&gt;两阶段提交的工程实践&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://mysql.taobao.org/monthly/2018/11/02/"&gt;PolarDB 数据库内核月报&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://karellincoln.github.io/2018/04/05/percolator-translate/"&gt;percolator：在线增量处理系统 中文翻译&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.notion.so/percolator-879c8f72f80b4966a2ec1e41edc74560?pvs=21"&gt;percolator：在线增量处理系统 中文翻译 | 一只小小鸟&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://zh.wikipedia.org/zh-hans/%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4"&gt;二阶段提交 - 维基百科，自由的百科全书&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cn.pingcap.com/blog/percolator-and-txn"&gt;Percolator 和 TiDB 事务算法&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.oceanbase.wiki/concept/transaction-management/transactions/distributed-transactions/two-phase-commit"&gt;两阶段提交 | OceanBase 学习指南&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.pingcap.com/zh/tidb/stable/tidb-architecture"&gt;TiDB 整体架构&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Dynamo: Amazon’s Highly Available Key-value Store</title><link>https://noneback.github.io/zh/blog/zh/dynamo/</link><pubDate>Tue, 01 Aug 2023 16:15:29 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/dynamo/</guid><description>&lt;p&gt;一篇AWS的老文章，Dynamo到现在已经售卖了很久了，架构应该也早已不像论文中描述的这样。但文章入选了某年的sigmod best paper，依旧是有很多值得学习的地方。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="设计"&gt;设计&lt;/h2&gt;
&lt;p&gt;Dynamo是一种NoSQL产品，对外提供KV存储语义。在产品定义上强调Highly Available而非Consistency，所以在架构设计以及技术选型上和其他的产品还是有很多不同之处。&lt;/p&gt;
&lt;h2 id="技术"&gt;技术&lt;/h2&gt;
&lt;p&gt;在技术上，Dynamo其实有很多有问题的地方，比如NWR算法本身的一些。但考虑到Dynamo已经经过了很长时间的验证，也许这些问题已经被很好的解决了，只不过论文中语焉不详。所以暂且放下这些，挑一些来说一说。其中我认为值得拿出来说的主要有这几块：&lt;/p&gt;
&lt;h3 id="数据的分区"&gt;数据的分区&lt;/h3&gt;
&lt;p&gt;一致性hash算法。传统的一致性hash算法使用hash ring来解决增减节点rehash范围大的问题，但比如数据倾斜，以及异构机器导致的性能倾斜这类问题是无法避免的。
Dynamo实践上，在原有hash ring上引入了虚拟节点，比较优雅的解决了这两个问题。&lt;/p&gt;
&lt;h3 id="数据写入问题"&gt;数据写入问题&lt;/h3&gt;
&lt;p&gt;一般存储系统会在写的同时保证一定的数据一致性，换取较低的读操作复杂度，代价上写性能降低（延迟等）。但Dynamo选择了另一条路线。
Dynamo的设计目标是提供一个高可用的KV Store，保证&lt;strong&gt;always writable&lt;/strong&gt;，同时只保证最终的一致性。这样的目标使得Dynamo把解决数据冲突之类的操作放到了读操作中，&lt;strong&gt;以保证写永远不会被拒绝&lt;/strong&gt;。
总的来说，问题有两个。一个是数据更新冲突问题，显然多client并发的读写同一个Key很容易遇到这样的问题，因为Dynamo只能提供最终一致性，Dynamo Ring 上多个节点的数据不一定一致。二是节点数据空洞的问题。因为Dynamo使用的是NMR这类的gossip算法，这类理论上会出现所有节点上都不包含完整数据集的情况，需要同步副本之间的数据。
前者使用版本时钟来标记数据版本，后续在读操作的时候进行读修复合并数据版本；后者有熵逆过程来处理，使用MerkleTree来&lt;strong&gt;快速检测副本之间的不一致性，以及最小化转移的数据量。&lt;/strong&gt;
&lt;img src="https://raw.githubusercontent.com/noneback/images/picgo/20230801162353.png" alt="" loading="lazy" decoding="async"&gt;
论文中的这个表格可以清楚的看出Dynamo设计开发需要考虑的方面以及在技术上的选择。剩下这些论文中感觉语焉不详，可以参考原文。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.raychase.net/2396"&gt;Dynamo 的实现技术和去中心化&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://timyang.net/data/dynamo-flawed-architecture-chinese/"&gt;Dynamo一个缺陷的架构设计(译) – 后端技术 by Tim Yang&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=915212"&gt;Dynamo: A flawed architecture | Hacker News&lt;/a&gt;&lt;/p&gt;</description></item><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>Kylin概述</title><link>https://noneback.github.io/zh/blog/zh/kylin%E6%A6%82%E8%BF%B0/</link><pubDate>Wed, 10 Nov 2021 23:45:27 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/kylin%E6%A6%82%E8%BF%B0/</guid><description>&lt;p&gt;之前就想着能做一个有意思的毕设，奈何周围都没有合适的老师。之前在学院启动选题之前找好了一个感觉不错的老师，但没想到最后把我鸽了。不过之前老师的方向也并不是那么感兴趣，于是也就作罢。 最近学院的毕设流程启动了，也在选题里看到了感兴趣的题目，于是便联系老师接了下来。&lt;/p&gt;
&lt;p&gt;我选的题目是 &lt;strong&gt;《基于差分隐私的数据库查询算法的设计与实现》&lt;/strong&gt;，方向是Differential Privacy + OLAP，具体一点就是为Kylin添加Differential Privacy的Feature。&lt;/p&gt;
&lt;p&gt;总的来说就是如此，至于细节，也许在之后的博客中会写到。这是此系列博客的第一篇。&lt;/p&gt;
&lt;h2 id="简介"&gt;简介&lt;/h2&gt;
&lt;p&gt;Kylin是一种分布式的OLAP数据仓库，基于Hbase和Parquet等列存数据库以及Hadoop和Spark等运算调度框架，支持超大规模数据的多维分析。&lt;/p&gt;
&lt;p&gt;它采用cube预计算的方法，把前台的实时查询变成了查询预计算结果，利用闲时的计算资源以及存储空间换取查询时间的优化，能极大的减少查询数据的时间。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;在Kylin之前，一般使用Hadoop来对大规模数据进行批处理，并将结果存储在Hbase等列式存储中。其中OLAP相关的技术便是&lt;strong&gt;大数据并行处理&lt;/strong&gt;和&lt;strong&gt;列式存储&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;大规模并行处理&lt;/strong&gt;：实际上可以调用多台机其来并行处理计算任务，本质上是利用线性增长的计算资源来换取计算时间的线性下降。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;列式存储&lt;/strong&gt;：将记录按照列来存储。这方面主要是和OLAP的查询有关的，OLAP一般是对数据做统计等计算，一般都是同类型的列数据。列式存储使得查询时可以只访问需要的列并且可以充分利用顺序IO，提高性能。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上两个技术使得Hadoop等平台上对于大数据的SQL查询达到了分钟级。但实际上，分钟级别的SQL查询依旧没法满足交互式分析的需要，效率依旧低下。&lt;/p&gt;
&lt;p&gt;其中本质的原因在于，&lt;strong&gt;无论是并行计算或者是列式存储，都没有改变查询本身的时间复杂度，没有改变查询时间和数据量的线性增长关系&lt;/strong&gt;。于是只能通过增加计算资源和利用局部性原理来对查询进行优化，这两种方法在数据量不断增长的情况下，都能明显的预见其成本上和理论上的瓶颈。&lt;/p&gt;
&lt;p&gt;基于此，Kylin提出&lt;strong&gt;预计算策略&lt;/strong&gt;，通过对不同的维度进行预计算生成多维&lt;strong&gt;cube&lt;/strong&gt;(实际上就是一个数据表),后续的查询直接基于预计算的结果进行。经过预计算,物化视图的规模就只由维度的基数来决定,而不再随着数据量的增长呈线性增长。&lt;/p&gt;
&lt;p&gt;这个策略本质上是&lt;strong&gt;利用空闲的计算资源以及额外的存储资源来换取查询时的响应速度，改变了查询时间与数据量之间的正比关系，从而提高效率&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="核心概念"&gt;核心概念&lt;/h2&gt;
&lt;p&gt;Apache Kylin 的工作原理本质上是&lt;strong&gt;MOLAP(Multidimensional Online Analytical Processing)Cube,多维立方体分析技术&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="维度和度量"&gt;维度和度量&lt;/h3&gt;
&lt;p&gt;维度指用于审视，聚合数据的一种角度，一般是数据记录的某一个属性。度量是基于数据计算出来的具体数值。通过维度来聚合计算出度量值 $$D_1,D_2,D_3,&amp;hellip; =&amp;gt; S_1,S_2，&amp;hellip;$$&lt;/p&gt;
&lt;h3 id="cube理论"&gt;cube理论&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Data Cube，数据立方体&lt;/strong&gt;，其主要涉及构建和查询两种操作，构建时对原始数据建立多维度索引以及预计算，以加速查询。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cuboid&lt;/strong&gt;: 指在某一维度组合下所计算出的数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cube Segment&lt;/strong&gt;：Cube Segment是Cube的最小构建单位，一个Cube能被拆分为多个Cube Segment。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cube增量构建&lt;/strong&gt;：一般来说，增量构建Cube时是基于时间属性来触发的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cube基数&lt;/strong&gt;：Cube中所有维度的基数可以体现Cube的复杂度。复杂度高,Cube膨胀的概率会变高（IO,存储放大）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="架构设计"&gt;架构设计&lt;/h2&gt;
&lt;p&gt;整个Kylin系统分为&lt;strong&gt;在线查询&lt;/strong&gt;和&lt;strong&gt;离线构建&lt;/strong&gt;两部分。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/11/10/AoxY4POJHdqLheb.png" alt="" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;离线构建&lt;/strong&gt;：主要有数据源，构建引擎，存储引擎三大抽象。从数据源拉取数据构建Cube在存储到列式存储引擎中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在线查询&lt;/strong&gt;：主要有接口层和查询引擎，对外部屏蔽Cube等底层概念。外部应用通过Rest API讲查询并转发给查询引擎，查询引擎查询与相关的数据返回结果。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;作为一个OLAP引擎，Kylin充分利用了&lt;strong&gt;并行计算，列式存储，预计算&lt;/strong&gt;等优化技术提高其在线查询和离线构建性能，故而有如下几个明显的优缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;: 标准SQL接口，查询速度快,可拓展架构，BI系统友好&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;:依赖的外部系统过多，运维困难；预计算与Cube构建导致的IO和存储放大；数据模型以及Cube基数的局限。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://tech.meituan.com/2020/11/19/apache-kylin-practice-in-meituan.html"&gt;美团 : Apache Kylin的实践与优化&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://kylin.apache.org/cn/"&gt;Kylin官方文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Kylin权威指南&lt;/p&gt;</description></item><item><title>DFS-Haystack</title><link>https://noneback.github.io/zh/blog/zh/dfs-haystack/</link><pubDate>Wed, 06 Oct 2021 22:44:01 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/dfs-haystack/</guid><description>&lt;p&gt;组内的主要项目便是一种提供POXIS文件系统语义的DFS，其中解决losf(lots of small files)的思路就是对小文件单独处理。里面的思想来源应该就是Haystack。
于是大致阅读了一下这篇论文，写下学习笔记。
笔记依旧不深究具体细节，仅仅记录对问题的思考以及设计的思路。&lt;/p&gt;
&lt;h2 id="介绍"&gt;介绍&lt;/h2&gt;
&lt;p&gt;HayStack是Facebook为了小文件设计的一种存储系统。之前的DFS，对于文件的寻址路径一般是都会使用Cache来缓存元数据，以便减少磁盘交互提高寻址效率。一个文件就需要一个维护一类元数据，文件数决定了元数据量。在高并发场景下，我们要减少磁盘IO，一般会选择将寻址需要的元数据缓存在内存中。&lt;/p&gt;
&lt;p&gt;在大量小文件场景下，会有大量的元数据。考虑到在内存元数据维护带来的开销，这种方案变得不可用。于是便有了为小文件特别优化的HayStack。它核心思想是将多个小文件聚合成一个大文件，以减少元数据。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;论文中的小文件其实是特指图片数据。&lt;/p&gt;
&lt;p&gt;Facebook是以社交起家的公司。在社交场景中，图片的上传和读取是常见需求。当业务发展到一定的程度，就需要有专门的服务支撑图片的大量高并发读写请求。&lt;/p&gt;
&lt;p&gt;在社交场景下，这类数据的特点是&lt;code&gt;written once, read often, never modified, and rarely deleted.&lt;/code&gt;基于此，Facebook开发了HayStack来支持图片分享服务。&lt;/p&gt;
&lt;h2 id="设计"&gt;设计&lt;/h2&gt;
&lt;h3 id="传统的设计"&gt;传统的设计&lt;/h3&gt;
&lt;p&gt;论文中给出了两种历史设计：基于CDN和基于NAS的方案&lt;/p&gt;
&lt;h4 id="基于cdn的方案"&gt;基于CDN的方案&lt;/h4&gt;
&lt;p&gt;这个方案的核心其实就是利用CDN对热点图片数据进行缓存，减少网络传输。&lt;/p&gt;
&lt;p&gt;常用的设计，对于热点图片的优化很显著。但问题也很明显，一是CDN价格昂贵容量有限；二是图片分享场景，也会有很多&lt;code&gt;less popular&lt;/code&gt;的图片数据，这就会导致长尾效应，拖慢速度。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guuaefh22gj610g0s4gnh02.jpg" alt="image-20210926200920113" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CDN其实一般服务于静态数据的，并且一般都是在活动之前进行预热，并不适合作为一种图片缓存服务使用。很多的&lt;code&gt;less popular&lt;/code&gt;的数据其实并未进入CDN，故而导致长尾效应。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="基于nas的方案"&gt;基于NAS的方案&lt;/h4&gt;
&lt;p&gt;这是facebook最初的设计方案，本质和基于CDN的方案区别不大，属于它的一种实现。&lt;/p&gt;
&lt;p&gt;引入NAS横向拓展存储，引入文件系统语义，同时也会有磁盘IO存在。和本地文件类似，对于未读取过的数据，至少需要三次磁盘IO：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read directory metadata into memory&lt;/li&gt;
&lt;li&gt;Load the inode into memory&lt;/li&gt;
&lt;li&gt;Read the content of the file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PhotoStore作为一层缓存使用，缓存一些元数据如file handle等，以加速寻址过程。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guuafar1rpj60u80scmyx02.jpg" alt="image-20210926201012724" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;基于NAS的设计并没有解决&lt;strong&gt;元数据过多导致不适合全量缓存&lt;/strong&gt;这个关键问题，一但文件数量到达临界值，不可避免的需要进行磁盘IO。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更本质的问题其实是&lt;strong&gt;文件与寻址元数据一一对应的关系&lt;/strong&gt;，使得元数据量随着文件数的变化而变化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以，优化的关键是，改变&lt;strong&gt;文件与寻址元数据一一对应的关系&lt;/strong&gt;，降低寻址过程中磁盘IO出现的频率。&lt;/p&gt;
&lt;h3 id="基于haystack的方案"&gt;基于HayStack的方案&lt;/h3&gt;
&lt;p&gt;此方案最核心的思路是&lt;strong&gt;将多个小文件聚合成大文件&lt;/strong&gt;，并只维护一份元数据。本质上是改变了元数据与文件数的映射关系，使将所有元数据保存在内存的方案成为可能。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;只维护聚合后的大文件的元数据，小文件在大文件中的位置需要另外维护映射&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNgy1guv5ytgsmtj60k20jqjsk02.jpg" alt="image-20210927142131959" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;h2 id="实现"&gt;实现&lt;/h2&gt;
&lt;p&gt;Haystack主要有三个组件，Haystack Directory、Haystack Cache、Haystack Store&lt;/p&gt;
&lt;h3 id="文件映射与数据落盘"&gt;文件映射与数据落盘&lt;/h3&gt;
&lt;p&gt;文件数据最终保存在logic volume上，一个logic volume对应多机器上的多个physical volume。&lt;/p&gt;
&lt;p&gt;用户首先访问Directory来获取访问路径，之后再通过Directory生成的URL访问其他组件，获取需要的数据。&lt;/p&gt;
&lt;h3 id="组件"&gt;组件&lt;/h3&gt;
&lt;h4 id="haystack-directory"&gt;Haystack Directory&lt;/h4&gt;
&lt;p&gt;属于Haystack的接入层，主要负责&lt;strong&gt;文件寻址&lt;/strong&gt;以及&lt;strong&gt;访问控制&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;读写请求首先访问Directory。对于读请求，Directory会生成一个访问URL，URL包含了访问的路径：&lt;code&gt;http://{cdn}/{cache}/{machine id}/{logicalvolume,Photo}&lt;/code&gt;。对于写请求，它会指定一个可以写入的volume提供用户写入。&lt;/p&gt;
&lt;p&gt;详细点来说，最主要有四个功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;负载均衡，平衡读写请求，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;决定请求的访问路径，比如是否通过CDN访问，生产访问URL&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;元数据与映射管理， 如&lt;code&gt;logic attr and logic volume =&amp;gt; list of phycial volume mapping&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;logic volume读写管理，logic volume可能是Readonly或者WriteEnabled&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这部分设计和数据特点有关，write once and read more。可以提高并发度。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于此，Directory就必须记录一些必须的元数据，file to volume，logical to phycial mapping、logical volume attr（size，owner、etc）。&lt;/p&gt;
&lt;p&gt;依赖分布式KV落盘元数据和缓存服务加速访问，以此保证容错可用以及低延迟。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Proxy，Metadata Mapping，Access Control&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="haystack-cache"&gt;Haystack Cache&lt;/h4&gt;
&lt;p&gt;Cache作为缓存用于优化寻址以及图像获取。核心的设计是&lt;strong&gt;Cache Rule&lt;/strong&gt;，判断什么样的数据需要被缓存以及&lt;strong&gt;Cache Miss&lt;/strong&gt;的处理。&lt;/p&gt;
&lt;p&gt;Haystack中，被缓存的图片数据需要满足这两个特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The request comes directly from a user and not the CDN&lt;/li&gt;
&lt;li&gt;The photo is fetched from a write-enabled Store machine&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当出现Cache Miss时，它会从Store上获取图片数据并同步推送至浏览器以及CDN中。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这样的策略是基于图片请求的场景与特点决定的。&lt;/p&gt;
&lt;p&gt;首先，在CDN中Miss的请求，很大概率上也会在Cache中miss，所以重CDN重定向的请求的数据不会被Cache。其次，图片往往在刚刚写入后不久时间内会被频繁的访问，所以这样的数据理应被Cache。&lt;/p&gt;
&lt;p&gt;CDN可以被视为一个External Cache。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="haystack-store"&gt;Haystack Store&lt;/h4&gt;
&lt;p&gt;Store属于Haystack的存储层，负责数据存储相关操作。&lt;/p&gt;
&lt;p&gt;首先是它的寻址抽象：&lt;code&gt;filename + offset =&amp;gt; logical volume id + offset =&amp;gt; data&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;多个Physical Volume组成Logical Volume，并作为实际落盘单位进行维护。在Store中，小文件被抽象成&lt;strong&gt;Needle&lt;/strong&gt;，交由Physical Volume进行管理和维护。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gv5oo0mltfj60zs0u0q5j02.jpg" alt="image-20211006164409801" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Needle实际上是对小文件的一种&lt;strong&gt;封装&lt;/strong&gt;，以及对Volume的&lt;strong&gt;分块&lt;/strong&gt;管理。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gv5ooyhloxj61150u043702.jpg" alt="image-20211006164428466" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Store的数据访问一般是以Needle维度进行的。为了加速文件寻址，一般会在内存中维护一个用于寻址元信息的Map：&lt;code&gt;key/alternate key =&amp;gt; needle's flag/offset/other attribute&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这些Map最后会被持久化到磁盘中的&lt;strong&gt;Index File&lt;/strong&gt;中，做为In-Memory Mapping的一种Checkpoint存在，用于Crash后寻址元数据的快速恢复。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gv5put6m7qj60u40jc0u102.jpg" alt="image-20211006172431986" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gv5puqgvgcj60te0dk0ua02.jpg" alt="image-20211006172515258" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每个Volume会维护一个自己的In-Memory Mapping和Index File&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在我们更新In-Memory Mapping的时候（比如修改文件、新增文件），会异步更新Index File。但在文件删除时，我们只异步标记文件删除，而不会修改Index File。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Index只是作为加速查找的手段，无Index的Needles依旧是可以被寻址的，故而上述策略是有效的。但是由于异步更新和不删除对应文件的Index的设计，引入了Orphan Index和Deleted Index的问题。&lt;/p&gt;
&lt;p&gt;Orphan Index是指无索引记录的Needle，一般Store会检查出这些Needle并为其添加Index。&lt;/p&gt;
&lt;p&gt;Deleted Index一般就直接在查询是检测出文件是Deleted的，并且是Mark Deleted的状态，此时会告知上层为查询到文件，同时及时更新In-Memory Mapping。这样的设计加速了文件NotFound的情况下的查询时间。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="工作负载"&gt;工作负载&lt;/h3&gt;
&lt;h4 id="读"&gt;读&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;(Logical Volume ID, key, alternate key, cookies) =&amp;gt; photo&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;当接收到从Cache收到读请求时，Store会去查询In-Memory Mapping查询对应的Needle。如果查询到，则根据volume + offset获取文件数据，并校验文件的cookie和完整性；否则返回错误。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cookie是在Directory生成URL的时候随机生成的字符串，在寻址的时候带上并校验可以有效防止恶意攻击。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="写"&gt;写&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;(Logical Volume ID, key, alternate key, cookies， data) =&amp;gt; result&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Haystack不支持文件的覆盖写入，只支持追加写。当收到写请求时，Store会异步Append文件数据到Needle中并更新In-Memory Mapping。如果这是一个老文件，那么Directory会更新它保存的元数据，以便后续的访问不会访问老版本的文件。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;除了Directory的元信息之外，怎么去判断数据版本的新旧？&lt;/p&gt;
&lt;p&gt;答案是根据volume以及offset。老的volume会被冻结为ReadOnly，新的volume的写入是追加的，所以offset大的必然更新。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="删除"&gt;删除&lt;/h4&gt;
&lt;p&gt;采取&lt;strong&gt;Mark Delete + Compact GC&lt;/strong&gt;的方式处理删除请求。&lt;/p&gt;
&lt;h3 id="容错设计"&gt;容错设计&lt;/h3&gt;
&lt;p&gt;对于Store使用&lt;strong&gt;监控 + 热备&lt;/strong&gt;的方式，Directory和Cache可以使用Raft等一致性算法保证数据副本一致与容错。&lt;/p&gt;
&lt;h2 id="优化"&gt;优化&lt;/h2&gt;
&lt;p&gt;优化手段主要有三点：Compaction、Batch Load、In Memory。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;优化的抽象：异步、批处理、缓存&lt;/li&gt;
&lt;li&gt;要发现主要问题，比如大量小文件最主要的问题是元数据带来的管理负担。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf"&gt;Finding a needle in Haystack: Facebook’s photo storage&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-Bigtable</title><link>https://noneback.github.io/zh/blog/zh/mit6.824-bigtable/</link><pubDate>Thu, 16 Sep 2021 22:54:59 +0800</pubDate><guid>https://noneback.github.io/zh/blog/zh/mit6.824-bigtable/</guid><description>&lt;p&gt;之前在网上找到了别人翻译的BigTable论文，就顺手保存了下来，但一直没开始看。最近发现BigTable和目前组内做的项目有很多设计上相似的地方，于是用周末的时间快速的阅读了一遍。&lt;/p&gt;
&lt;p&gt;这是Google分布式三大论文的最后一篇，本不属于MIT6.824课程的阅读材料的。但姑且先如此分类。&lt;/p&gt;
&lt;p&gt;笔记依旧不深究具体细节，仅仅记录对问题的思考以及设计的思路。&lt;/p&gt;
&lt;h2 id="介绍"&gt;介绍&lt;/h2&gt;
&lt;p&gt;Bigtable 是一个分布式的&lt;strong&gt;结构化数据&lt;/strong&gt;存储系统，构建于GFS之上，用于存储大量的结构化，半结构化数据。它属于NoSql数据存储，优势在于可拓展性和性能，以及基于GFS上的可靠容错。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Design Goal：Wide Applicability、Scalability、High Performance、High Availability&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="数据模型"&gt;数据模型&lt;/h2&gt;
&lt;p&gt;Bigtable的数据模型属于No Scheme的设计，仅提供了一个简单的数据模型，它将所有的数据视为字符串，数据的编解码交由上层业务方决定。&lt;/p&gt;
&lt;p&gt;实际上，Bigtable 是一个&lt;strong&gt;稀疏的、分布式的、持久化存储的多维度排序 Map&lt;/strong&gt;。Map的&lt;strong&gt;索引&lt;/strong&gt;是&lt;strong&gt;Row Key，Column Key以及TimeStamp&lt;/strong&gt;。Map中的&lt;strong&gt;每一个Value就是一个无结构的Byte数组&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="code-block" data-language="go"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;go&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 映射抽象&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 一个Row Key实际上是 {Row , Column, Timestamp} 组成的多维结构。&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1gufarm0ykaj615k0d0jts02.jpg" alt="image-20210913205832997" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;论文对数据模型原文阐述如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A Bigtable is a sparse, distributed, persistent multidimensional sorted map.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sparse，实际上是说同个Table中的Column属性是可以置空的，并且置空比较常见。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Row&lt;/th&gt;
&lt;th&gt;Columns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Row1&lt;/td&gt;
&lt;td&gt;{ID，Name，Phone}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row2&lt;/td&gt;
&lt;td&gt;{ID，Name，Phone，Address}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row3&lt;/td&gt;
&lt;td&gt;{ID，Name，Phone，Email}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Distributed，此处分布式关键在于拓展性和容错性，也就是&lt;strong&gt;Replication&lt;/strong&gt;和&lt;strong&gt;Sharding&lt;/strong&gt;。Bigtable通过GFS的Replica实现副本容错，使用&lt;strong&gt;Tablet&lt;/strong&gt;对数据分区，以实现拓展性。&lt;/p&gt;
&lt;p&gt;Persistent Multidimentsional Sorted，持久化说明最终需要数据落盘，Bittable相关的设计有WAL，LSM优化前台读写时延，优化落盘速度。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BigTable的开源实现就是HBase，是一种行列数据库&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="rows"&gt;Rows&lt;/h3&gt;
&lt;p&gt;Bigtable通过行关键字的字典序来组织数据，Row Key可以是任意的字符串。对于单行的读写操作是原子的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;字典序的好处在于可以将相关的行记录聚合&lt;/p&gt;
&lt;p&gt;可以参考Mysql的实现，利用undo log的方式实现行操作的原子性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="column-family"&gt;Column Family&lt;/h3&gt;
&lt;p&gt;Column Keys 组成的集合叫做 Column Family，一个Column Family下的数据往往属于同一种类型。&lt;/p&gt;
&lt;p&gt;一个Column Key由 Column Family : Qualifier组， 列族的名字必须是可打印的字符串,而限定词的名字可以是任意的字符串。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;论文中有提到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Access control and both disk and memory accounting are performed at the column-family level.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原因在于，业务方取得数据实际上更多的是以Column为单位取得相关的数据，比如读取网页content等操作。实践上，往往也将列数据压缩存储。基于此，Column Family显然是一个比Row更合适的Level。&lt;/p&gt;
&lt;p&gt;如我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据) 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="time-stamp"&gt;Time Stamp&lt;/h3&gt;
&lt;p&gt;TimeStamp主要是为了维护不同时间的不同数据版本，属于一种逻辑时钟。同时它也作为索引，不同版本的数据可以通过时间戳索引查询。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;设计上，一般按时间新旧排序，推测在数据版本少的时候可以利用一个指向上一个时间版本的指针来维护数据时间视图，在数据版本多的时候需要进化为索引结构。&lt;/p&gt;
&lt;p&gt;显然基于时间戳必然有范围查询的需求，选择可排序数据结构作为索引比较合&lt;/p&gt;
&lt;p&gt;但TimeStamp需要Table额外维护一个数据版本视图，带来一定的管理负担。一般的解决方法是限制数据版本的数量，将过期的数据进行GC处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="tablet"&gt;Tablet&lt;/h3&gt;
&lt;p&gt;Bigtable在存储设计上，采用的是&lt;strong&gt;range-based&lt;/strong&gt;的&lt;strong&gt;数据分片方式&lt;/strong&gt;，而Tablet是Bigtable中data sharding 和 load balance的基本单位。&lt;/p&gt;
&lt;p&gt;Tablet其实就是有若干个Row组成的一块数据，并由一个Tablet Server进行管理。行数据在Bigtable系统内最终保存在一个Tablet上，并在适当的时候进行Tablet拆分合并，在Tablet Server之间负载均衡。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Range Base的分片方式的好处是有利于范围查询，与之相对的是Hash分片的方式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="sstable"&gt;SSTable&lt;/h3&gt;
&lt;p&gt;SSTable 是一个&lt;strong&gt;持久化的、排序的、不可更改&lt;/strong&gt;的Map 结构,而 Map 是一个 key-value 映射的数据结构,key 和 value 的值都是任意的 Byte 串。&lt;/p&gt;
&lt;p&gt;Tablet实际上是Bigtable系统中对外的存储单位，实际上，内部的存储文件是Google SSTable格式的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从内部看，SSTable 是一系列的数据块(通常每个块的大小是 64KB)，通过索引加速定位数据。读取时先读索引，二分查索引，在读取数据块。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="api"&gt;API&lt;/h3&gt;
&lt;p&gt;原文中的API如下，主要是为了体现和RDB不同的地方。&lt;/p&gt;
&lt;div class="code-block" data-language="cpp"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;cpp&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Writing to Bigtable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Open the table Table *T = OpenOrDie(&amp;#34;/bigtable/web/webtable&amp;#34;); // Write a new anchor and delete an old anchor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;RowMutation&lt;/span&gt; &lt;span class="nf"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;com.cnn.www&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anchor:www.c-span.org&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;CNN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anchor:www.abc.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Operation&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Reading from Bigtable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Scanner&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ScanStream&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FetchColumnFamily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anchor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SetReturnAllVersions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;com.cnn.www&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s %s %lld %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ColumnName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;MicroTimestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="架构设计"&gt;架构设计&lt;/h2&gt;
&lt;h3 id="外部组件"&gt;外部组件&lt;/h3&gt;
&lt;p&gt;Bigtable是基于Google内部其他组件之上构建的，这极大的简化了Bigtable的设计。&lt;/p&gt;
&lt;h4 id="gfs"&gt;GFS&lt;/h4&gt;
&lt;p&gt;GFS是Bigtable底层的GFS，可以提供Replication，提供一定的数据容错性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以参考上一篇笔记内容&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="chubby"&gt;Chubby&lt;/h4&gt;
&lt;p&gt;Chubby是一个高可用的分布式锁组件，它提供了一个NameSpace，其中的目录和文件均可作为一个分布式锁来使用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;高可用是指维护了多个可提供服务副本，并使用paxos算法保证副本间的一致性。同时使用租约机制，防止失效的Chubby客户端一直持有锁。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为什么需要Chubby？它的作用是什么？&lt;/p&gt;
&lt;p&gt;原文中提到的功能有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;存储Bigtable的Column Family信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储ACL。ACL 是一种机制，用于定义用户访问存储对象的权限&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储了 元数据的起始位置，也就是Root Tablet的位置信息&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bigtable使用了一个三层的类B+树的存储数据结构，Chubby中保存了Root Tablet的位置信息，Root Tablet保存了其他MetaData的Tablet信息，其他MetaData的Tablet则保存着其他用户的Tablet集合信息。&lt;/p&gt;
&lt;p&gt;在Bigtable启动时，会先从Chubby中获取Root tablet，然后再获取其他映射信息。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tablet Server的生命周期监控&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tablet Server在启动时，会在Chubby&lt;strong&gt;指定目录&lt;/strong&gt;下生成一个唯一名称的文件，并获取此文件的排它锁。当Tablet Server失效时，会释放锁。&lt;/p&gt;
&lt;p&gt;Master通过监控这个目录中的文件以及持有锁的状态，来监控集群中的Tablet的状态以及配置变更&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总结一下，Chubby的功能其实主要分为两类。一是利用Chubby作为高可用高可靠的存储节点来存储关键元信息；二是利用其提供的分布式锁服务来管理维护存储节点（Tablet Server）。&lt;/p&gt;
&lt;p&gt;在GFS中，这些功能其实是由Master去负责管理维护的。在Bigtable中，由Chubby接管，这样简化了Master的设计，并减轻了Master的负载。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;抽象意义上，Chubby也能视为Master节点的一部分。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="内部组件"&gt;内部组件&lt;/h3&gt;
&lt;h4 id="master"&gt;Master&lt;/h4&gt;
&lt;p&gt;Bigtable也属于Master-Slave的架构，这点个GFS以及MR的设计很像。不同之处在于，Bigtable将元数据交给了Chubby + Tablet Server去存储与管理，Master只负责调度需要的信息，不存储Tablet位置信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tablet分配，GC；Tablet Server监控，负载均衡；表的元数据修改等&lt;/p&gt;
&lt;p&gt;所以调度需要的信息包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;所有Tablet的信息，用于分析分配和分布情况&lt;/li&gt;
&lt;li&gt;Tablet Server的状态信息，判断是否满足分配条件&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4 id="tablet-server"&gt;Tablet Server&lt;/h4&gt;
&lt;p&gt;Tablet Server 管理一系列的Tablet，负责处理Tablet的数据读写，以及Tablet的拆分合并等操作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Master不保存元信息，Client读取信息直接与Chubby和Tablet Server交互。&lt;/p&gt;
&lt;p&gt;Tablet的拆分由Tablet Server主动发起，并及时通知Master。这一步可能会消息丢失，所以最好使用WAL+重试的方式保证操作不丢失&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="client-sdk"&gt;Client SDK&lt;/h4&gt;
&lt;p&gt;作为Bigtable的接入层，业务方使用ClientSDK接入Bigtable。Client SDK作为Bigtable的入口，优化的关键在于怎么样可以减少获取元数据的次数。一般使用cache以及prefetch的思路去减少网络的交互，充分利用时间和空间局部性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;缓存的使用势必会带来不一致问题，需要设计合适的方案解决此问题。如不一致时重新寻址。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="存储相关"&gt;存储相关&lt;/h2&gt;
&lt;h3 id="映射关系-寻址"&gt;映射关系 、寻址&lt;/h3&gt;
&lt;p&gt;Bigtable的数据实际上是由(Table, Row, Column)三元组唯一确定的，保存在Tablet中，Tablet最终保存在GFS SSTable中。&lt;/p&gt;
&lt;p&gt;Tablet逻辑上可以理解为Bigtable的落盘实体，实际上是交由Tablet Server去管理维护，那么这样就需要Bigtable去维护一定的映射关系。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;基于原文描述，可能需要维护的映射：&lt;/p&gt;
&lt;div class="code-block" data-language="cpp"&gt;
&lt;div class="code-block-header"&gt;
&lt;div class="code-block-meta"&gt;
&lt;span class="code-block-language"&gt;cpp&lt;/span&gt;
&lt;/div&gt;
&lt;div class="code-block-actions"&gt;
&lt;button class="copy-code" type="button" aria-label="复制"&gt;复制&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Table&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;Tablets&lt;/span&gt; &lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Tablet&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tablet&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Tablet&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt; &lt;span class="c1"&gt;// Tablet contains
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Tablet&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="c1"&gt;// where Tablet shores
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;SSTable&lt;/span&gt; &lt;span class="c1"&gt;// where the tablet stores in GFS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Indexes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Row&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tablet&lt;/span&gt; &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SSTable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bigtable通过&lt;code&gt;Root Tablet + METADATA Table&lt;/code&gt;进行Tablet寻址。Chubby存储了Root Tablet的位置信息，METADATA Table则由Tablet Server负责维护。&lt;/p&gt;
&lt;p&gt;Root Tablet中保存了其他METADATA Tablet的位置信息。而METADATA Table的每一个Tablet包含了一系列的User Tablets的位置信息（可以理解为UserTable handle）, 每个Tablet的位置信息保存在METADATA的Row中。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;METADATA Table 中的Row :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(TableID,encoding of last row in Tablet) =&amp;gt; Tablet Location&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guhk1wuu1pj60q80ge75k02.jpg" alt="image-20210915142508074" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;系统采用了类似B+树的三层结构来维护tablet location信息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="调度和监控"&gt;调度和监控&lt;/h3&gt;
&lt;h4 id="调度"&gt;调度&lt;/h4&gt;
&lt;p&gt;其实主要就是对Tablet的调度，包括分配和负载均衡。&lt;/p&gt;
&lt;p&gt;其实两种归根到底都是分配问题。在任何一个时刻, 一个 Tablet 只能分配给一个 Tablet 服务器。Master保存了Tablet Server 相关的状态信息，当有Tablet需要分配时，就发送请求将Tablet分配出去。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Master不维护寻址相关的状态信息，但需要维护Tablet Server的状态信息（持有的Tablets数量、状态、剩余资源等），可以通过心跳周期上报到Master。所以说，Master其实是无状态的，负载也比较轻。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="监控"&gt;监控&lt;/h4&gt;
&lt;p&gt;监控是由Chubby + Master来完成的。&lt;/p&gt;
&lt;p&gt;Tablet Server在启动时，会在Chubby&lt;strong&gt;指定目录&lt;/strong&gt;下生成一个&lt;strong&gt;唯一名称的文件&lt;/strong&gt;，并获取此文件的排它锁。当Tablet Server断开连接，lease失效后会自动释放文件锁。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tablet Server通过唯一文件决定是否对外提供服务。此文件可能被Master删除&lt;/p&gt;
&lt;p&gt;Tablet Server可能由于网络因素重新连接，此时只要文件存在，Tablet重连时又会重新尝试去获取这个文件的排他锁。&lt;/p&gt;
&lt;p&gt;如果不存在，当原Tablet Server重连是，应当自动退出集群。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Master首先要在Chubby获取一个唯一文件排他锁保证Master节点的唯一性，并指定一个Tablet Server文件目录。&lt;/p&gt;
&lt;p&gt;然后Master通过不断监控这个目录中的文件以及持有锁的状态，来监控集群中的Tablet的状态以及配置变更，并获取正在运行的Tablet Server列表。&lt;/p&gt;
&lt;p&gt;一但发现有Tablet Server失效，Master就会把Chubby属于原Tablet Server的唯一文件删除，并把原来这个Tablet Server维护的Tablet集合重新分配给其他Tablet Server。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tablet Server失效有两种情况，一种是无法与Chubby连接，二是成为孤岛或者宕机。前者可以通过文件锁状态判断，后者通过Master发送心跳。其余情况，可能是Chubby无法提供服务。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;之后在和所有Active的Tablet Server进行通信，获取Tablet Server的状态信息；扫描METADATA表获取未分配的Tablet信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;监控的目的其实就是为了调度。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="compaction"&gt;Compaction&lt;/h2&gt;
&lt;p&gt;Bigtable对外提供读写服务，并且使用了LSM的结构对写性能进行优化。对于一个写操作，首先通过Chubby中保存的ACL信息，判断用户的权限；通过之后再以WAL的方式顺序写记录操作再CommitLog和Memtable中，并且最后会被持久化到SSTable中；&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guhk6llq76j60uk0lsq4402.jpg" alt="image-20210915195135522" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;由图中可以看出，memtable是保存在内存中的，无法无限的增加。所以Bigtable在memtable增长到一定大小的时候会进行一次Minor Compaction，将memtable的数据写入一个SSTable中，并写入GFS。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上，memtable在转为SSTable之前会先转换成immutable memtable，并生成新的memtable支持前台写入。这样的中间状态其实是为了Minor Compaction不影响前台写。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://tva1.sinaimg.cn/large/008i3skNly1guill1zltrj615g0p8adp02.jpg" alt="image-20210916172932210" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;Bigtable在处理写入数据时提出了&lt;strong&gt;Compaction&lt;/strong&gt;概念，加速前台写。所有的前台随机写被转换为顺序写。并在后台的Compact进程中再次对数据进行写入。以读写放大为代价，优化前台感知到的写性能。&lt;/p&gt;
&lt;p&gt;所谓Compaction，本质上就是对数据的一次再次扫描，在扫描写入过程中，对数据进行合并压缩和GC。&lt;/p&gt;
&lt;p&gt;一般Compaction以多个不可变数据作为输入，Compact之后会将数据重新写入另一个新的只读有序结构(如SSTable)或者随机写落盘。&lt;/p&gt;
&lt;p&gt;前台写对Compaction的参与数据不影响，这使得Compaction步骤原生支持并行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;原文中提到了三种Compaction：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Minor Compaction&lt;/strong&gt;：将memtable转化成SSTable的过程就是Minor Compaction，写入过程中会丢弃被删除的数据，并只保留数据的终态。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Minor Compaction的目的在于减少Tablet Server的内存使用，以及CommitLog的大小。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Merge Compaction&lt;/strong&gt;：Memtable和SSTable一起Compact得到一个新的SSTable&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Major Compaction&lt;/strong&gt;：多个SSTable也可能需要Compact成一个SSTable
但对于读请求，我们可能需要聚合Memtable以及一定的SSTable来做读查询，因为查询的数据可能存在于memtable或者SSTable中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原文中引入了&lt;strong&gt;二级缓存&lt;/strong&gt;和&lt;strong&gt;Bloom Filter&lt;/strong&gt;来加速读查询。&lt;/p&gt;
&lt;p&gt;Tablet Server有两级缓存。第一层是&lt;strong&gt;扫描缓存&lt;/strong&gt;，主要缓存从SSTable读取过的的KV pair；第二级是&lt;strong&gt;Block缓存&lt;/strong&gt;，缓存读取的SSTable Block。&lt;/p&gt;
&lt;p&gt;对于经常要重复读取相同数据的应用程序来说,扫描缓存非常有效；对于经常要读取刚刚读过的数据附近的数据的应用程序来说，Block 缓存更有用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bloom Filter&lt;/strong&gt;简单来说就是可以判断某个Key是否不存在。&lt;/p&gt;
&lt;p&gt;可以为全局或者每个Tablet Server，以及SSTable分别维护一个Bloom Filter，用于加速读查询，减少到SSTable的查询；对于可能存在的Key，利用二分法查询&lt;/p&gt;
&lt;h2 id="优化"&gt;优化&lt;/h2&gt;
&lt;p&gt;除了上文中说的到优化方式，原文还提到了几种优化手段。&lt;/p&gt;
&lt;h3 id="局部性"&gt;局部性&lt;/h3&gt;
&lt;p&gt;通过配置或者自动等方式，把高频使用的列数据组合生成一个SSTable存储，减少分开Fetch的时间。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;空间换时间，充分利用局部性原理&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="压缩"&gt;压缩&lt;/h3&gt;
&lt;p&gt;将SSTable中的分块进行二级压缩处理。本质其实是为了减少网络传输的带宽和时延，但需要额外的压缩和解压计算。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;分块压缩是为了减少编解码的时间以及提高并行效率&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="commitlog-设计"&gt;CommitLog 设计&lt;/h3&gt;
&lt;p&gt;关键其实是Log的粒度问题。Tablet粒度下，会有大量的Log文件。在并行写入GFS中会有大量的磁盘Seek操作，并且不利于Batch化。集群单独一个CommitLog会带来恢复上的高复杂度。&lt;/p&gt;
&lt;p&gt;Bigtable中是每个Tablet Server一个Commit Log。但由于Tablet Server维护了多个Tablet，这就使得Bigtable必须维护LogEntry到TabletID的映射以及顺序，以便在恢复时使用。对一个Tablet的数据恢复可能会导致扫描整个CommitLog，&lt;/p&gt;
&lt;p&gt;此处Bigtable的优化是将Log按照(Table, row, log seq num)并行分块排序，使得同一个Tablet的LogEntry聚合。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;不要过渡设计，Simple is Better Than Complex。&lt;/p&gt;
&lt;p&gt;对于分布式服务，集群监控非常重要。Google三篇论文中对集群状态的监控都是不可缺少的一环。监控的意义也在于支持集群的调度。&lt;/p&gt;
&lt;p&gt;设计时不要对其他系统做出任何假设，出现的不仅仅是常见的网络问题，现实中可能会遇到各类问题。&lt;/p&gt;
&lt;p&gt;利用后台处理加速前台感知。利用简单的机制处理前台请求，再开启后台进程善后。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://zh.wikipedia.org/wiki/Bigtable"&gt;wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf"&gt;Bigtable&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/xybaby/p/9096748.html"&gt;典型分布式系统分析：Bigtable&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://zhuanlan.zhihu.com/p/181498475"&gt;LSM树详解&lt;/a&gt;&lt;/p&gt;</description></item><item><title>MIT6.824-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>