现在全闪存阵列已经见怪不怪了,EMC的XtremIO,还有VNX-F(Rockies),IBM FlashSystem。全闪存真正为效率而生,重新定义存储速度。凭借极致性能,高可用性,为您极大提高企业级应用效率。提到闪存的优势,那么毋庸置疑的就是速度!而在速度优势背后,SSD则面临着价格、容量以及寿命等方面的限制。
当然随着技术的发展,成本的下降,SSD有可能会取代机械硬盘,成为下一代企业存储的主要介质。机械硬盘可能转变为磁带的角色。
但是,闪存速度的确就是现在存储系统的极限吗?现在有需要基于内存的数据库,比如Redis,TimesTen。也不得不提缓存系统的极佳实践memcached。spark也把操作的中间数据全都放入到内存中,避免了Hadoop实时性和可用性差的问题,有可能对Hadoop的生态圈产生深远影响。spark已经于2014年2月27日正式成为Apache基金会的*项目了。
RAMCloud,是一个完全使用DRAM的存储系统,它的所有数据都保存到内存中。当然了为了故障恢复RAMCloud会将日志和数据的备份持久化到普通硬盘中。在2014年的FAST会议中,RAMCloud发表了关于内存分配及管理机制的paper,并且被评为best paper,拜读此文后,形成此文以分享心得。
1. Why not use malloc?
现在又很多memory allocator,比如C的malloc,Google的tcmalloc。当然了还有Java的内存管理及GC。但是它们有几个问题,一个是效率底下,在访问模式变化多端时极容易产生内存碎片。实验表明,使用malloc只能使内存的利用率最大到50%。
memory allocator可以分成两类:non-copying and copying。non-copying allocator就是在内存分配后不会再移动它。对于单一的程序来说,这种分配方式非常自然,因为毕竟这些内存一旦申请它的大小也基本上不会再变了。但是对于存储系统来说,文件可以修改,比如增大或者减小,那么如果使用malloc,需要重新申请新的内存块,那么就非常容易产生碎片。比如系统刚开始新建了一片100B大小的文件,后来部分文件增大到130B,那么,释放的100B的空间有可能会产生碎片,比如以后没有再写<=100B大小的文件。(注:内存碎片的确在这种内存存储系统中非常容易产生,而且作者设计的测试用例的确也使得malloc的内存利用率最大也就到50%。但是实际生产环境是否是这个样子现在不得而知。总之RAMCloud的内存分配和管理的确挺好,但是malloc是否在生产环境下是否是真的如此不堪,现在也无法定论。关于malloc的内存分配,请阅《malloc内存分配与free内存释放的原理》)。
对于copying allocator,通过garbage collector的确可以解决碎片的问题。但是缺点就是需要遍历所有的数据才能够重新分配。但是,处于性能的考虑,需要很多额外的内存(1.5倍到5倍)。这样也就失去了通过碎片管理来提高内存使用率的初衷了。而且还有一个问题是需要长时间的服务暂停。拿Java的GC来说,它需要3-4秒,这个时间对于RAMCloud来说,已经可以检测到一个故障的节点并且恢复64GB的数据了。(注: 不知道是否是不恰当的使用Java使得在此蒙冤了,尽管的确它是有问题)
2. RAMCloud概述
由于本文主要为了阐述基于日志的内存分配管理,因此本节主要介绍storage的management,和为什么需要基于日志的内存分配管理机制,而摒弃原有的。
RAMCloud最适合的场景是已经将服务器分为应用服务器(主要实现生成网页和执行业务规则等应用逻辑)和存储服务器(为应用服务器提供长期共享存储)的数据中心。这些数据中心一般支持许多应用,有的很小,只使用一台服务器的一部分能力,有的很大,要用到数千台专用应用和存储服务器。
而且,内存云中保存的信息必须和硬盘一样持久,单个存储服务器的故障不能造成数据丢失和哪怕几秒钟的服务不可用。RAMCloud将所有数据存放在DRAM中,性能可以达到比目前最高性能的硬盘存储系统还要高100~1000倍。在访问延迟方面,RAMCloud方案中运行在应用服务器中的一个进程从同一数据中心的存储服务器中通过网络读取数百字节数据只需5~10μs,而目前实际系统一般要花费0.5~10ms,具体取决于数据是在服务器内存缓存中,还是硬盘中。而且,一台多核存储服务器每秒可以服务至少100万次小读取请求。而硬盘系统中同样的机器每秒只能服务1000~10000次请求。
系统架构图如下。
每个storage server都包含两个部分: Master和Backup。Master管理了存储在memory中的object。Backup使用本地的机械硬盘或者固态硬盘保存了其他server的数据备份。Coordinator管理了Master和Backup配置信息,比如集群各个server之间的关系和各个备份的分配。但是Coordinator并不涉入数据的读写操作,因此也不会成为cluster的bottleneck或者降低系统的scalability。
RAMCloud提供了一个简单的key-value的数据模型,数据(称为object)都是连续存储的。每个object都被长度不一的唯一的key标记。多个object被保存到table中,这个table有可能跨越多个server。object只能以整体的方式进行读写。它为小object做了专门的优化,这也非常适合超大规模的web并发的请求。
每个Master都有自己的日志,这些日志被分成8M的块,成为segment(段)。每个segment都会冗余到其他的server的Backup,典型的配置都是冗余2-3块。在client写操作的时候,冗余发送到其他的节点,这些节点在把冗余写到memory的buffer后就会返回,而不是保存到本地磁盘后才返回,这样保证了client的高速度写入。这些buffer在某些时间点会flush到本地存储。
3. Log Metadata
在基于日志的文件系统中,为了快速的访问日志数据,日志中使用了各种索引。RAMCloud是使用了hash table来访问在memory中的数据。硬盘中的日志在正常工作的环境下是永远用不到的。它唯一被用到的机会就是故障恢复。日志一共分为三种:
1). 元数据
包含了存储基本单元object的table id,key,version,value。在数据恢复时,将根据最新version的条目来重建hash table。
2). 日志摘要(log digest)
每一个新的日志段都会有一个日志摘要,这个摘要包含了所有属于这个段的日志。在数据恢复时,将根据段的最新的日志摘要来加载所有的元数据。
3). tombstone(墓碑?)
这个日志是比较特殊的。它代表了被删除的object。日志一旦写入就不可修改,那么如果一个object被删除怎么办?在日志中添加一条tombstone的记录。记录包含table id,key和version。在平时的操作中,tombstone是不会用到。但是在数据恢复时,它能保证被删除的object不会被重建。
tombstone的机制很简单但是也有很多问题。其中一个问题就是它的GC,毕竟它最终要在log中删除。只有在object被删除后,tombstone才能被删除。否则也就违背了引入tombstone的初衷了:通过它来防止被删除的object的重建。在cleaner处理tombstone的log时,它会检查tombstone所指定的segment是否不在任何的log中。如果不在,那么说明object已经被删除,否则说明该segment还有有效数据,该tombstone不能被删除。
4. Two-level Cleaning
对于基于日志的内存分配,主要的瓶颈就在于log cleaner。碎片管理,或者说内存回收是非常昂贵的,这就需要巧妙的设计。特别是随着内存利用率的上升,回收的成本也越来越大。例如,对于一个内存利用率80%的情况,为了获取2B的可用空间,平均情况下需要移动8B的数据。如果是90%,那么移动9B的数据只能带来1B的空间。见下图的上半部分。
对于Memory来说,带宽不是问题,我们关注的是空间的利用率,毕竟相对来说DRAM还是比较贵的。对于硬盘来说,带宽是昂贵的,可以说很多系统的瓶颈都是在硬盘的IO读写速度上,这也催生了很多基于memory的各种解决方案,当然也是RAMCloud需要解决的问题。因此,RAMCloud对于memory和disk采取了两种不同的回收策略。
两个阶段的cleaning,使得memory的cleaning可以不影响Backup,这个结果就是一般来说memory的利用率要高于disk。memory的利用率可以达到90%,disk的要低得多。当然因为disk的cleaning的频率更低,这也就可以理解了。
第一个level的cleaning,成为segment compaction,仅仅是处理了in-memory的segment,并没有消耗网络或者disk的IO。它每次压缩一个segment,将它上面的数据拷贝到其他的segment,这样空出来的segment可以为其它所用。segment compaction在memory和disk中都维持了相同的log。但是由于在memory中的segment中删除的object和tombstone都已经被彻底删除了,因此它实际上占用了更少的空间。入下图所示。
第二个level的cleaning称为combined cleaning。顾名思义,这一level的cleaning不单单会cleaning disk的,同样也会cleaning memory的。因为已经有segment compaction了,因此这一level的cleaning也是很有效率的。而且因为这一阶段被延后进行了,可以合并更多的删除操作了。
4.1 seglet
如果没有segment compaction,那么所有的segment都有相同的大小,默认情况下是8MB。因为有了压缩,因此segment可以有不同的大小。当然我们不能采用传统的heap allocator,因为那会产生碎片。RAMCloud将segment分为64KB的seglet。每个segment分为不同的seglet,seglet的数量就决定了segment的大小。你可能也有疑问,seglet的引入肯定也会引入碎片。实际上,对于一个segment来说,只可能产生大约1/2个seglet大小的碎片,也就是32KB/8MB = 1/64=1.5%。也是由于seglet的引入,log现在变得不连续了,因为log有可能跨越多个seglet,RAMCloud使用了额外的机制来处理这种情况。
4.2 clean的触发机制
two-level的cleaning的引入,引入了一个新的问题:什么时候触发segment compaction,什么时候触发combined cleaning?这个选择将会影响到系统的性能,因为combined cleaning占用了系统珍贵的disk IO和network IO。这个policy 模块,称为balancer。接下来简要的介绍一下balancer的机制。
一般情况下,在memory和disk的利用率不是很高的情况下,cleaner是不会工作的。因为cleaner的提早工作并不会有多高的效率。相反,如果cleaner的工作延后,那么可以处理更多的删除的object,可以回收更多的空间资源,因此,延后的cleaner是非常有意义而且有好处的。
那么如何判断memory的剩余空间已不足呢?balancer通过以下方式进行判断:假设L代表live objects所占用的空间使用率,F代表未分配的seglet所占整个空间的使用率,那么如果F <= min( 0.1, ( 1 - L )/2 ),那么cleaner就要开始工作了。一方面,cleaner会尽量的延后工作使得回收工作更有效率,另一方面,当预测到系统有可能会run out-of-memory时,cleaner会立即启动以回收memory以保证有更多的memory可以使用。
那么选择segment compaction呢,还是选择combined cleaner呢?一般来说,compaction会优先考虑因为它更有效率。但是有两种情况下必须启动combined cleaner:第一种情况就是tombstone太多了。因为segment compaction是不能单独把tombstone标记的数据删除的,它必须将这些数据从backup中删除之后才可以删除在memory中的tombstone。由于tombstone的越来越多,使得memory的使用率越来越高,导致compaction越来越低效。最终,选择combined cleaner可以是系统删除那些tombstone,也会使得以后的segment compaction更有效率。
那么如何计算tombstone的多寡呢?balancer通过以下公式:假设T代表live tombstone所占用的空间使用率,L as above,T/( 1 - L ) >= 40%。也就是说,当tombstone占用了超过40%的可用空间的时候,combined cleaner会启动以删除这些tombstone,回收空间。40%是通过不同的条件,不同的负载下测试得出的经验值。从这个公式也可以得出,在有很多小文件的使用场景下,这种cleaner会频繁的调用。
第二个原因就是on-disk log太大时,为了防止disk run out-of-space,也为了避免系统恢复时重建时间太久,都会启动combined cleaner。
小结一下,balancer在未使用的空间率太低时会启动segment compaction并且a)disk space使用率较低 和b) tombstone不多。
5. Parallel Cleaning
现在CPU的主频已经打破了摩尔定律了,但是CPU的核心数不断增加。现在16 core,32 core的server CPU已经非常常见了。RAMCloud也充分利用了CPU 的multi-Core: 同时以多线程执行cleaner。
因为log的结构和简单的meta-data,使得cleaner的并行执行变得简单。因为log是不可修改的,cleaner在复制移动object时就不必担心这些object会被修改。而且,hash table存储的也是object的间接地址,因此更新hash table的object reference变得很简单。因此基本的clean机制也变得简单起来:
cleaner 拷贝live data到新的segment,也就自动的更新了hash_table的object reference,最后将cleaned segment就释放出来了。
在clean线程和service线程在处理读写请求时有三个地方需要注意:
- 它们都需要在log的head添加数据
- 它们在更新hash table可能会有冲突
- 当segment在被某些service线程使用的时候,cleaner不能释放它们。
5.1 日志的并发更新
最简单的cleaning方法就是把需要移动的数据添加到log的头部。但是这样话的会和service的写请求冲突。为了避免这种情况,RAMCloud是将这些需要移动的数据移动到一个新的segment。每个cleaner都会申请一组segment用于保存移动的数据,只有在申请segment的时候需要同步。在申请完成后,每个cleaner就可以操作自己所属的segment了而无需额外的同步操作。在移动完成后,cleaner会把这些segment放到下一个的log digest中,同时被回收的segment也会在下一个log digest中也会被删除。而且将这些数据移动到不同的segment而不是head segment中还有另外的好处:这些segment可以备份到不同的disk中,可以提高Master的吞吐量。
5.2 Hash Table的争夺
Hash Table由于同时被cleaner和service threads使用,因此会有同步的问题。Hash table标明了哪些object是可用的并且保存了在memory中的地址。cleaner使用Hash table去确定某个object是否alive:通过判断指向的地址是否真的是该object,如果是alive的,他就将Hash table中更新该object中的新地址。同时,service线程通过Hash table来读
取或者删除某个object。RAMCloud现在使用在每个hash的bucket上使用一个fine-gained lock来同步。
5.3 释放内存空间的时机
当cleaner thread clean完成一个segment后,这个segment的空间可以被释放或者被重用。在这个时间点,所有service thread都不会看到这个segment的数据,因为没有hash table的条目指向这个segment。但是有可能老的service thread还在使用这个segment。所以free这个segment可能会导致非常严重的问题。
RAMCloud采用了一个非常简单的机制来处理这个问题:系统直到所有当前的service thead数据请求处理完成才会释放该segment。当然的service thread处理完成之后,释放segment是安全的,因为所有后来的service thread都不可能使用到这个segment。这个设计很简单,避免了使用锁来完成这个普通的读写操作。
5.4 释放disk空间的时机
当segment被clean后,在backup上的冗余备份也需要删除。然而,这个删除只能在被移动到的segment正确的写到on-disk中的日志之后。这需要两个步骤。
- 合并后的segment需要在backup中完成冗余拷贝。因为所有的冗余拷贝都是异步进行传输的,因此只有cleaner在接收到所有的响应接到后。
- 新的log digest必须包含新的合并的segment和删除被clean的segment。
只有上述数据被持久化后,这个冗余备份就可以被安全的删除了。
6. Avoiding Cleaner Deadlock
由于clean的过程中需要额外的内存,因此在内存利用率高的时候,clean的过程中有可能会耗掉最后的内存,run out-of-memory。因此形成了deadlock。
RAMCould为了避免这种deadlock采取了多种技术,首先,它会为cleaner申请一个seglet的pool。使得不会因为seglet的申请而导致死锁。而且,它在clean一个segment时,会计算是否这个clean会带来空间的利用率升高。比如下图这个例子的clean会导致碎片更大,反而导致了空间的浪费。
还有一个措施是会申请专门的log digest以免申请额外的log digest时导致内存耗尽。
综合以上技术,RAMCloud的空间利用率可以达到98%,而不会产生deadlock。
7. RAMCloud的未来
首先声明本节内容不是来自于paper,而是搜集自网络。
7.1 对比SSD
基于DRAM存储设备比基于闪存的存储设备速度更快,但成本也要高得多。
比如,2TB大小基于闪存的存储设备成本大约为18万美元;相比之下,存储容量相同但基于DRAM的存储设备成本高达约100万美元。基于DRAM的驱动器读取或写入数据的时间只要0.015毫秒,工作状态下随机速度达到了每秒可以处理40万次I/O。这种驱动器最适合以写操作为主的软件以及使用高性能数据库应用系统的公司。
基于闪存的存储驱动器读取或写入数据的时间为0.2毫秒,工作状态下最高读取速度为每秒10万次I/O,最高写入速度为每秒2.5万次I/O。这项技术也更适合以读操作为主的应用。
7.2 应用场景
首先,RAMCloud可以是一个数据密集型应用的新架构,传统的架构是应用程序连同代码和数据被加载到一台服务器的主存储中,瓶颈也是显而易见的,各种复杂的数据操作,应用程序的大小,机器的处理能力都是瓶颈。
而在过去的10年中,一种服务于数百万用户的大型WEB应用架构出现了。其主要将应用程序代码和数据存放于同一个数据中心中的不同服务器中。应用服务器只存储当前请求和处理浏览器的需求,而这种架构允许应用程序扩展到成千上万的应用服务器和存储。
但是不幸的是,在大型架构图中,当服务器增加了4-5个数量级后,应用程序的复杂性,数据的访问延迟都成了问题。比如当Facebook收到一个HTTP请求访问网页时,应用服务器必须发出130个以上的数据以生成HTML页面,这当中有指令请求的顺序,而这些请求指令的累积是造成给用户整体响应时间延迟的因素之一,所以需要相当大的开发量,以尽量减少对服务器请求的代码大小和数量。
Mapreduce是最近几年兴起的一个新的技术,目的在于提高数据接入速度,消除了延迟问题,现在它解决了大规模的问题,但是如果是连续的数据访问,将使得Mapreduce仅仅限于在随机访问数据的应用中使用。
RAMCloud则充分结合了两者的优势——规模化和低延迟:保留了Web应用程序的可扩展性,同时降低了数据访问延迟以接近传统的应用程序。
过去所有的Web应用程序都使用关系型数据库存储,但随着数据规模的扩大,一个单一的关系型数据库已经不能满足他们的I/O需求。因此大家开始做系统升级,引进新的技术来扩展自己的存储系统(比如多个数据库间的数据分区)。
比如,尽管Facebook在2009年的时候就有4000个MySQL服务器,但由于大量交互式数据的调用,现有的存储系统依旧不能满足它的I/O需求,所以Facebook用了2000个Memcached用作分布式内存对象缓存服务器——将一些键值存储于主内存中,但其瓶颈在于,需要处理Memcached和MySQL服务器之间的一致性,需要对应用软件进行管理(比如刷新缓存值以更新数据库),这无疑增加了应用的复杂性。
因此,NoSQL开始出现,用非关系型数据库以键值对存储,它的结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这样就不会局限于固定的结构,可以减少一些时间和空间的开销,但是他们瓶颈依然是磁盘速度。
RAMCloud的原理之一是提供一个通用的存储系统,其规模远远超出现有的系统,应用程序开发人员不需要采取特殊的方式(如NoSQL系统)。理想的状态是,RAMCloud提供一个简单的模型,易用、并且有扩展性,并对应用程序的城战不需要做架构上的改变。
7.3 实际案例
目前一个可行的RAMCloud配置,每台服务器配置24GB的DRAM,这是高性价比的配置。扩展内存会导致成本的急剧增加。2000服务器会配备48TB的存储空间,平均每GB成本65美元。据预测,到2020年,随着DRAM技术的不断完善,激励1PB-10PB配置的RAMCloud时每GB成本仅需6美元。
RAMCloud已经在实际中有所应用。例如一个大型的网络零售商或航空公司使用RAMCloud的花费在几十万美元。截止2009年8月Facebook所有非图像数据大约有260TB。这可能接近了当今RAMCloud实用的上限。
像电视频、照片、歌曲等数据还没有大规模应用RAMCloud,然而RAMCloud实际已经可以用在所有在线的数据。随着DRAM技术的不断改进,RAMCloud在未来会更具吸引力。
7.4 号外
连12306都采用内存计算平台了,或许,基于memory的计算,
中国铁路客户服务中心12306网站选择Pivotal GemFire分布式内存计算平台改造方案,根据系统运行数据记录,在只采用10几台X86服务器实现了以前数十台小型机的余票计算和查询能力,单次查询的最长时间从之前的15秒左右下降到0.2秒以下,缩短了75倍以上。
7.5 DRAM存储系统是未来?
图灵奖得主Jim Gray很早就提出了“内存将成为硬盘,硬盘将成为磁带”的说法(出自2006年Tim Bray一篇讨论网格计算的博客,2003年的访谈中他已经表达了同样的意思)。2008年Dare Obsanjo在分析Twitter的架构时也看到,类似的新型应用的最大负担是硬盘I/O,因此会倾向于将随机操作都放到RAM里,只将顺序操作留给硬盘。
尊重原创,转载请注明出处: http://www.anzhan.me/index.php/archives/195
参考资料:
1. https://www.usenix.org/system/files/conference/fast14/fast14-paper_rumble.pdf
2. http://highscalability.com/are-cloud-based-memory-architectures-next-big-thing
3. http://www.ciotimes.com/cloud/ccc/52711.html