<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>DFS on NoneBack</title><link>https://noneback.github.io/zh/tags/dfs/</link><description>Recent content in DFS on NoneBack created by</description><generator>Hugo -- gohugo.io</generator><language>zh</language><copyright>@NoneBack All rights reserved</copyright><lastBuildDate>Wed, 06 Oct 2021 22:44:01 +0800</lastBuildDate><atom:link href="https://noneback.github.io/zh/tags/dfs/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>