Microsoft云存储服务分为两个部分,SQL Azure和Azure Storage。云存储系统的可扩展性和功能不可兼得,必须牺牲一定的关系数据库功能换取可扩展性。Microsoft实现云存储的思路有两种:
1, 做减法。SQL Azure直接在原有的SQL Server上引入分布式的因素,在满足一定可扩展性的前提下尽可能不牺牲原有的关系型数据库功能。SQL Azure的可扩展性是有限的,单个SQL Azure实例不允许超过50GB,这是因为SQL Azure不支持子表动态分裂,单个SQL Azure实例必须足够小从而可以被一个节点服务。具体信息可以参考我以前的一篇文章:Microsoft SQL Azure架构设计。
2, 做加法。Azure Storage先做好底层可扩展性,然后再逐步加入功能,这与Google GFS & Bigtable的思路比较一致。Azure Storage分为Blob, Queue和Table三个部分,其中Azure Table Storage最具有代表性,由于底层系统支持子表分裂,单个用户的最大数据量可以达到100TB。然而Table Storage支持的功能有限,甚至不支持索引功能。具体信息可以参考msdn的一篇文章:Azure Storage架构介绍。
Microsoft Azure Storage 逻辑上分为三层:
1, 前端(Front-End Layer):类似互联网公司的Web服务器层,可采用LVS + Nginx的设计,主要做一些杂事,比如权限验证。由于前端服务器没有状态,因此很容易实现可扩展。
2, 存储层 (Partition Layer):类似Bigtable,分为Partition Master和Partition Server两种角色,分别对应Bigtable Master和Tablet Server。每一个Partition(相当于Bigtable中的Tablet)同时只能被一个Partition Server服务,Partition Master会执行负载均衡等工作。Bigtable中分为Root Table, Meta Table和User Table,Azure Table Storage可能会为了简单起见消除Meta Table,所有的元数据操作全部放到Partition Master上。
3, 文件系统层(DFS Layer):类似GFS,数据按照extent(相当于GFS中的chunk)划分,每个extent大小在100MB ~ 1GB之间。数据存储三份,写操作经过Primary同步到多个Secondary,读操作可以选择负载较轻的某个Primary或者Secondary副本。当发生机器故障时,需要选择其它机器上的Secondary切换为Primary继续提供写服务,另外需要通过增加副本操作使得每个extent的副本数维持在安全值,比如三份。为了简单起见,DFS Layer对上层Partition Layer可以不提供文件系统接口,只提供类似块设备的访问方式。
Azure Storage的主键包括Partition Key + Row Key两部分,其中,Partition Key用于数据划分,规定相同的Partition Key只属于同一个Partition,从而被一台Partition Server服务,这就使得Partition Key相同的多个行之间能够支持事务。与SQL Azure不同,Azure Storage的并发操作通过乐观锁的方式实现。Azure Storage包含三个不同的产品,其中Azure Table Storage支持用户设置Schema,支持byte[], bool, DataTime, double, Guid, Int32, Int64, String这几种列类型;Azure Blob Storage将Blob数据存放到底层的DFS Layer中,Blob过大时可能存放到多个extent中,Partition Layer存储每个Blob的编号到Blob所在的多个extent位置之间的映射关系;Azure Queue Storage将Partition Key设置为Queue的编号,Row Key设置为消息的编号,从而保证属于同一个Queue的消息连续存放。
总之,Microsoft Azure Storage和早期的Bigtable总体架构是很类似的,可能做了一些简化,这也间接说明了一点:如果不发生重大硬件变革,工程上要实现高可扩展的分布式B+树,将云存储系统分成文件+表格两层还是比较靠谱的。开发人员更多地需要在实现细节上下功夫,落实到线上的每行代码上。