Azure Services探索—存储之表(Table)存储

del.icio.us Tags: Windows Azure,Table

本系列文章是一个有关Azure Services开发基础性的学习记录,由于时间有限,所以希望自己讨论和探索的过程是从零开始,到能够进行Azure Services基本的编程开发。相对于每个议题可能有非常深入的话题,我希望有时间能通过其他的文章来进行。本系列的定位基本上定位于,花20-30分钟时间,先下载代码,跟着阅读文章,运行获得相关的体验。

上一篇是关于Azure的队列存储,本篇是关于表存储的。理论上表存储应该是我们最熟悉的存储方式,它本身也是结构化的存储,类似于我们平时的关系型数据库的存储。不过这里首先要强调的还是编程思想的变化,Windows Azure 平台是云计算的平台,所以从设计开始,它的定位是托管方式的,全开放和分布式的。体现到Azure Service的表存储上也是一样,首先整个的表存储是租户式的设计,说到这里,一定有很多人会想起Saas中的多重租赁(Multi-tenancy)概念,并开始将Azure存储和其联系在一起联想翩翩了。Windows Azure 平台的存储设计是非常精巧的,我们先把焦点转回Azure Service是如何定义和操作表存储的。其中我们需要知道哪些理念。

规则1 Azure的表存储也是跟随和隶属于一个帐号的,一个账号下面可以建立多个表存储 ,和队列一样表名跟随在帐号名的范围内,你可以从访问的REST路径看出规律: https://<Account>.table.core.windows.net/<TableName>

规则2 数据是保存在表存储中的,一个表是一个实体的集合,保存了一组实体的信息,每一个实体记录是一个行记录;一个实体是该实体的关键属性和相关的属性的集合,保存了一组实体属性的信息,实体的每一个属性是一个名/值对,对应一列。Entities <->Rows ,Properties<-> Columns .

基本上Azure 表存储的概念如下图:

clip_image002

Pictures source: https://blogs.msdn.com/jnak/archive/2008/10/28/walkthrough-simple-table-storage.aspx

套用上面的规则2,你可以发现一个联系人表是一个联系人实体的集合,保存了一组联系人的信息,每一个联系人记录是一个行记录;一个联系人是该联系人的关键属性和相关的属性的集合,保存了一组联系人属性(Name,Address)的信息,实体的每一个属性是一个名/值对(名字/小王,地址/北京王府井),对应一列。

 

规则3 一个实体固定有两个关键属性,这两个关键属性联合唯一标示一个实体。第一个关键属性是:PartitionKey,它是实体在数据库信息中的分区标示/区域关键字,第二个属性是RowKey,它是在某个分区内能够唯一标示该实体的关键字。

下图可以清晰地看到规则3的定义

clip_image004

Pictures source: ES07.pptx of PDC 2008-Windows Azure Tables:Programming Cloud Table Storage

 

文档(Document)是一个Azure的表存储,保存了一组文档实体。每一个文档对应一行记录,每一个文档包含关键属性(Document,Version) 和一些相关属性 (ChangedOn, Description);每一个属性是一个名值对,对应到数据库的一列。

根据规则3,PartitionKey其实对文档的一个细分或说颗粒化,用来作为分类/分区的标示,这个文档是FAQ文档还是样板文档,而RowKey属性在一个分类中,可以唯一标示和定位到这个文档。比如v2.0.1加上分类就可以唯一定位到v2.0.1版本的Examples文档。

其实这如何细分来确定这个颗粒度,是由应用来决定的,从上图可以看到,你可以在一个表里定义多个不同的文档分类关键字,也可以将整个表的PartitionKey都定义成一个,一样的关键字,这样分区就扩大到整个一张表,版本号就成为真正的关键字了。从规则3可以看出Azure的表存储的设计原则,一是侧重于直接贴近业务场景和实体对象,第二是要方便查询。

规则4 Azure运行环境会根据PartitionKey来对实体数据进行聚集和索引,PartitionKey是你进行实体查询的首要关键字,相当于你SQL 查询中最主要的Where条件。从伸缩性上来看,Azure运行环境会将不同分区的实体/数据放到不同的存储节点上,它尽力会将一个分区的数据(PartitionKey关键字相同的数据)放在一个存储节点上,而不是多个存储节点上,同时会对这个存储节点优先作分区的负载均衡(Automatically load balance partitions)

clip_image006

Pictures source: ES07.pptx of PDC 2008-Windows Azure Tables:Programming Cloud Table Storage

 

从上图看,我们根据规则4可以得出下面的结论:因为只要搜索单个分区,所以你查询所有FAQ Doc类的文档速度会很快;同样,如果你查询从截至到2008年5月30日之前所有的FAQ Doc类文档则速度会很慢,因为 这个查询需要检索多个分区,而如果每个分区落在不同的存储节点上,那就需要花费更多的时间。

 

现在我们假设你要为中国移动设计一个Azure 版本的应用,就表存储设计来说,你将整个中国移动的客户放在一个表里可能定是不现实的,你一定会考虑分区,PartitionKey可以是地域属性,比如你按省来分区;RowKey是手机号码;你也可以将PartitionKey设定成移动业务的品牌;RowKey是手机号码;也许这样你还决得大,你会将一个省的客户数据放到一个表里,实体变成了广东省移动客户,这样PartitionKey可以变成省下面的地州市,比如广州市,深圳….RowKey是手机号码,甚至你还是觉得不够细,你可以将广州市的用户放到一个表里,实体变成了广州市移动客户,PartitionKey可以变成市下面的区,比如白云区,天河区,RowKey可以换成业务类型;甚至你还想细分……所以,上面说了数据的细分或说颗粒化取决于应用,但你应该了解Windows Azure的设计和规则。

针对规则4,这也可能涉及到实体的单表扩展还是多表扩展,也会涉及到一个实体对应多个表,多个实体对应一个表,一个实体在不同状态下有不同数量/动态的属性等等这样的设计难题和争议。我就不涉及太多。

 

规则5 每个实体最多拥有255个属性,但自定义的只有253个甚至更少一些,因为所有的实体都有两个固定的关键属性PartitionKey和RowKey。属性的定义和定义脚本是相当灵活和没有限制的,比如可以将两个不同属性/不同属性个数的实体定义在一个表中。

了解了上面的这些规则,只完成了我们第一项工作,Azure表存储的概念和设计概念。真正的如何定义表的操作,以及访问表存储是我们接下来要做的。

Azure 表存储的访问技术是最眩和最新的技术,比如完全兼容ADO.NET data services,使用最新的.NET 3.5 SP1中的.NET Client 类库访问,编程查询语言使用LINQ……. Windows Azure平台的开发模型涵盖了微软最新的所有的先进技术,这样不枉费各位粉丝从.NET 1.0到.NET 4.0的追随和勤奋学习,这下总算有用武之地了。

而后我就转到一个最简单的表存储的Azure 的应用,来演练和探索一些Azure表存储的基本操作和编程技术。因为如果了解了上述的规则和设计要点,我认为基本是掌握Azure表存储技术的关键,后面的过程都是看图说话和一些体力活-操作键盘和鼠标。

 

接下来你可以选择按照Jim Nakashima的文章Windows Azure Walkthrough: Simple Table Storage来体验,也可以继续按下面的步骤体验,我的操作选自Azure Services Training Kit - PDC Preview的一个练习,两者的差别是Jim Nakashima的文章涉及更多的一些话题Create Tables Only Once这样有关高性能的示范,因为Jim Nakashima决定他应该提供高质量的示范代码J

这个练习主要是熟悉表存储的创建和访问,运行后的界面如下图:

clip_image008

对于这样一个简单的聊天留言板,数据表更是简单,基本上是一个叫聊天消息的实体,实体有两个属性/字段,聊天人的名字(Name)和聊天内容(Body),当然根据上面的规则,还有ParitionKey 和RowKey这两个关键和固定字段。

首先依然是向上一篇访问队列那样,进行Azure服务的定义和配置。分别定义TableStorageEndpoint,AccountName和AccountSharedKey的属性

 

服务定义文件ServiceDefinition.csdef, 这个例子还没有用到Worker Role ,也可不配置

 <?xml version="1.0" encoding="utf-8"?>
  
 <ServiceDefinition name="WorkingWithTables" xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  
 <WebRole name="WebRole">
  
 <ConfigurationSettings>
  
 <Setting name="TableStorageEndpoint"/>
  
 <Setting name="AccountName"/>
  
 <Setting name="AccountSharedKey"/>
  
 </ConfigurationSettings>
  
 <InputEndpoints>
  
 <!-- Must use port 80 for http and port 443 for https when running in the cloud -->
  
 <InputEndpoint name="HttpIn" protocol="http" port="80" />
  
 </InputEndpoints>
  
 </WebRole>
  
 <WorkerRole name="WorkerRole">
  
 <ConfigurationSettings>
  
 <Setting name="TableStorageEndpoint"/>
  
 <Setting name="AccountName"/>
  
 <Setting name="AccountSharedKey"/>
  
 </ConfigurationSettings>
  
 </WorkerRole>
  
 </ServiceDefinition>
  

 

服务配置文件ServiceConfiguration.cscfg中,定义在服务定义文件中各个参数的值

 <?xml version="1.0"?>
  
 <ServiceConfiguration serviceName="WorkingWithTables" xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
  
 <Role name="WebRole">
  
 <Instances count="1"/>
  
 <ConfigurationSettings>
  
 <Setting name="TableStorageEndpoint" value="https://127.0.0.1:10002"/>
  
 <Setting name="AccountName" value="devstoreaccount1"/>
  
 <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
  
 </ConfigurationSettings>
  
 </Role>
  
 <Role name="WorkerRole">
  
 <Instances count="1"/>
  
 <ConfigurationSettings>
  
 <Setting name="TableStorageEndpoint" value="https://127.0.0.1:10002"/>
  
 <Setting name="AccountName" value="devstoreaccount1"/>
  
 <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
  
 </ConfigurationSettings>
  
 </Role>
  
 </ServiceConfiguration>
  

Azure 服务的表存储存取API,依然是采用了SDK中的StorageClient 类库,然后分别在Web Role项目中添加StorageClient的引用

clip_image010

接下来的步骤是关于表存储种中比较重要的一个工作,那就是建立一个实体类与表模型的脚本

进一步的说,我们不用写数据库的建表脚本来建立在Azure存储系统中建了表存储,而是根据上述的规则,我们告诉Windows Azure我们的实体是什么,ParitionKey 和RowKey分别是什么,怎么赋值,即满足规则2和规则3的要求,Windows Azure自动就会帮你建立表,建立索引甚至存储节点。之后你通过公开的API就可以访问这些表了。

 

具体的操作是,我们需要从Microsoft.Samples.ServiceHosting.StorageClient. TableStorageEntity 继承派生出我们的聊天消息实体类,这个类的定义和描述,其实就是实体类与表模型的脚本,之后我们可以通过开发工具或代码执行,告诉Windows Azure建立这个实体的表存储。差别是如果通过开发工具表存储是预先建立的,代码建立表可以在运行时,访问表存储之前建立。这也就是上面说的Create Tables Only Once的问题了,因为这存在一个什么是创建表的最好时机的问题,本练习时通过开发工具来预先建立表存储。

 

聊天消息实体类的定义如下:

    1: using Microsoft.Samples.ServiceHosting.StorageClient; 
    2:  
    3: namespace WorkingWithTables_WebRole
    4:  
    5: {
    6:  
    7: public class myMessages : TableStorageEntity 
    8:  
    9: {
   10:  
   11: public myMessages()
   12:  
   13: {
   14:  
   15: PartitionKey = "mymessages";
   16:  
   17: RowKey = string.Format("{0:10}_{1}", DateTime.MaxValue.Ticks - DateTime.Now.Ticks, Guid.NewGuid()); 
   18:  
   19: // TimeSpan 
   20:  
   21: }
   22:  
   23: public string Name { get; set; }
   24:  
   25: public string Body { get; set; }
   26:  
   27: }
   28:  

在构造函数里,我们定义了最关键的ParitionKey 和RowKey是如何定义,因为这个表非常简单,所以ParitionKey的赋值我给的是一个固定的值,没有按照规则4和规则5的要求来优化,之后是我们的自定义的属性Name和Body。

之后顺手再定义一个表的访问类,非常像DAL,因为上面的只是一个表定义类,我们的应用最终是需要访问这个表存储中的数据。这个我们需要从Microsoft.Samples.ServiceHosting.StorageClient. TableStorageDataServiceContext 继承派生一个类,就可以创建自己的数据查询和访问的类:

    1: using Microsoft.Samples.ServiceHosting.StorageClient; 
    2:  
    3: public class MessageDataServiceContext : TableStorageDataServiceContext
    4:  
    5: {
    6:  
    7: public MessageDataServiceContext(StorageAccountInfo accountinfo)
    8:  
    9: : base(accountinfo)
   10:  
   11: { 
   12:  
   13: }
   14:  
   15: public IQueryable<myMessages> Messages
   16:  
   17: {
   18:  
   19: get
   20:  
   21: {
   22:  
   23: return this.CreateQuery<myMessages>("Messages");
   24:  
   25: }
   26:  
   27: }
   28:  
   29: public int AddMessage(string name, string body)
   30:  
   31: {
   32:  
   33: int ret = -1;
   34:  
   35: try
   36:  
   37: {
   38:  
   39: this.AddObject("Messages", new myMessages { Name = name, Body = body });
   40:  
   41: this.SaveChanges();
   42:  
   43: ret = 0; 
   44:  
   45: }
   46:  
   47: catch (Exception e)
   48:  
   49: {
   50:  
   51: throw e;
   52:  
   53: }
   54:  
   55: finally
   56:  
   57: {
   58:  
   59: }
   60:  
   61: return ret; 
   62:  
   63: }
   64:  
   65: }
   66:  

 

之后在Azure的开发环境中预先建立表存储

操作如下:在Visual Studio中选中的你的项目,然后右键,点击”Create Test Storage Tables”

如下图:

clip_image012

之后会显示下面的提示框表示表已经成功创建了。

clip_image014

你可以查看Visual Studio的输出窗口:

clip_image016

你会发现Visual Studio使用了SDK中的命令行工具DevtableGen.exe,执行了下面的命令:

C:\Program Files\Windows Azure SDK\v1.0\bin\DevtableGen.exe" /forceCreate "/server:localhost\SQLExpress" "/database:WorkingWithTables" "obj\Debug\WorkingWithTables_WebRole\bin\StorageClient.dll;obj\Debug\WorkingWithTables_WebRole\bin\WorkingWithTables_WebRole.dll;C:\myProject\WorkingWithTables\WorkingWithTables\WorkingWithTables_WorkerRole\bin\Debug\StorageClient.dll;C:\myProject\WorkingWithTables\WorkingWithTables\WorkingWithTables_WorkerRole\bin\Debug\WorkingWithTables_WorkerRole.dll"

 

我们不妨到数据库中看一下建立的这个数据库和表

clip_image018

最后我们在WebRole中增加一些代码,来存取访问表存储的简单代码

查询和数据绑定

    1: protected void Page_Load(object sender, EventArgs e)
    2:  
    3: {
    4:  
    5: string statusMessage = String.Empty;
    6:  
    7: try
    8:  
    9: {
   10:  
   11: StorageAccountInfo accountInfo = StorageAccountInfo.GetAccountInfoFromConfiguration("TableStorageEndpoint");
   12:  
   13: // dynamically create the tables
   14:  
   15: TableStorage.CreateTablesFromModel(typeof(MessageDataServiceContext), accountInfo);
   16:  
   17: MessageDataServiceContext context = new MessageDataServiceContext(accountInfo);
   18:  
   19: this.messageList.DataSource = context.Messages.Take(10);
   20:  
   21: this.messageList.DataBind();
   22:  
   23: }
   24:  
   25: catch (DataServiceRequestException ex)
   26:  
   27: {
   28:  
   29: statusMessage = "Unable to connect to the table storage server. Please check that the service is running.<br>"
   30:  
   31: + ex.Message;
   32:  

 

向表存储中增加记录

    1: protected void SubmitButton_Click(object sender, EventArgs e)
    2:  
    3: {
    4:  
    5: StorageAccountInfo accountInfo = StorageAccountInfo.GetAccountInfoFromConfiguration("TableStorageEndpoint");
    6:  
    7: MessageDataServiceContext context = new MessageDataServiceContext(accountInfo);
    8:  
    9: context.AddMessage(this.nameBox.Text, this.messageBox.Text);
   10:  
   11: }
   12:  

 

F5 运行后,先检查开发环境中的表存储服务是否启动。

clip_image020

运行成功之后,我们还可以看一下SQL Server 数据库中的情况,比如Azure 开发环境对ParitionKey 和RowKey的理解和赋值情况

clip_image022

通过上面的体验,我们发现Azure的表存储的定义和操作,与传统的表定义和操作还是有些不同,另外在编程体验上基本上完全整合了ADO.NET Data ServiceADO.NET Entry Framework的风格和技术。

不过我们需要有比较清晰的概念和理解,就是目前Windows Azure 可能实现的是ADO.NET Data ServiceADO.NET Entry Framework的一个子集,如果对ADO.NET Entry Framework比较熟悉的,可能非常想知道ADO.NET Entry Framework的特性有多少在Azure上实现了,这个需要我们在之后慢慢探索。

最后花一点时间,讨论一下Jim Nakashima的文章Windows Azure Walkthrough: Simple Table Storage中的要点,也就是在真正部署到正事Windows Azure运行环境时,比较流行的方法是使用上面讲的第二种方式,就是运行时刻创建表存储,而且最好是只创建一次。这个文章中讲了,主要是使用TableStorage.CreateTablesFromModel方法来实现。Jim解释说在运行环境下建表过程是程序化完成的,但是在Azure 的开发环境(Local Development Storage)使用工具预先建立表示必须的步骤,这是Azure 的开发环境的限制。 我想这非常可能是在Widnows Azure 的运行环境中,表存储不是简单理解成在某个一个SQL数据库上建立一个表这么简单,可能其过程是非常复杂和神奇的。 而Mark Seemann给出了使用 另外一方法,就是用PowerShell的方法在Azure 运行环境创建表,基本上就是用Shell 脚本调用TableStorage.CreateTablesFromModel方法。

 

Azure 服务的表存储的表的命名规则在上篇有关队列命名规则1中已有论述的,另外Azure 服务的表存储是保存在Azure运行环境的数据库中,所以也是可靠和持久性的存储(Durable Storage)。

 

相关的示范代码可以在我的MSDN代码库中下载获得。代码在在Azure SDK and Tools Jan 2009 CTP环境下测试通过。