Azure 表存储服务可存储大量结构化数据。该服务是一个 NoSQL 数据存储,接受来自 Azure 云内部和外部的通过验证的呼叫。Azure 表最适合存储结构化非关系型数据。表服务的常见用途包括:
也就是说Azure 表存储服务适合存储数据量非常大的结构化非关系型数据。表服务包含以下组件:
在开发前你需要了解如下的概念:
我们建立StorageTableController来测试Table Storage的服务,代码如下:
这个控制器有如下方法
代码明确简单:
[Authorize] public class StorageTableController : Controller { CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("Microsoft.WindowsAzure.Storage")); CloudTableClient tableClient = null; public StorageTableController() { tableClient = storageAccount.CreateCloudTableClient(); } }
对应的View
@model IEnumerable<Microsoft.WindowsAzure.Storage.Table.CloudTable> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p> @Html.ActionLink("创建新的表格", "Create") </p> <table class="table"> <tr> <th>Name</th> <th>Uri</th> </tr> @foreach (var item in Model) { <tr> <td>@Html.ActionLink(@item.Name, "List", new { name = item.Name })</td> <td>@item.Uri</td> <td> @Html.ActionLink("上传数据", "Upload", new { name = item.Name }) | @Html.ActionLink("删除", "Delete", new { name = item.Name }) </td> </tr> } </table>
Table本身并没有太多的属性,所以Index非常简洁,运行结果如图
代码如下
[HttpPost] public ActionResult Create(string name) { CloudTable table = tableClient.GetTableReference(name); table.CreateIfNotExists(); return RedirectToAction("Index"); }
对应的View代码
@{ ViewBag.Title = "Create"; } <h2>Create</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Storage Table</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> <div class="col-md-offset-2 col-md-10"> @Html.Label("Table 名称") @Html.TextBox("name") </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div>
运行结果如下
创建成功后会跳转到Index页面,我们可以看到创建成功的表
Storage Table存储的是结构化数据,所以我们需要建立一个模型,该模型必须从TableEntity继承,该基类实现了两个重要属性:PartitionKey和RowKey。
我上传的是我们业务的模拟数据,本来计划上传1000万行,实际测试太耗时间了,所以最终上传了300万行数据。
Table上传实体可以批量上传以提高效率,但有如下限制:
因为我们同事提供我数据的时候没有排序整理,所以我需要编写些逻辑进行适当处理。数据文件叫ARow1000W.txt,看看名字就知道我们的计划本来是很大的,下图看下这个文件的内容大致结构:
在这个文件中,PK是有重复值的,但RK没有重复值。
对应这个文件我们编写的模型如下代码:
public class BizEntity : TableEntity { public string TARMAGIC { set; get; } public string CONTSEQ { set; get; } public string CONTDATE { set; get; } public string CONTCODE { set; get; } public string CONTNOTE1 { set; get; } public string CONTNOTE2 { set; get; } public string CONTNOTE3 { set; get; } public string FOLLDATE { set; get; } public string FOLLCODE {set;get; } public string CONTWHO { set; get; } public string CONTTEMP { set; get; } }
控制器代码如下:
public ActionResult Upload(string name) { CloudTable table = tableClient.GetTableReference(name); var path = @"../ARow1000W.txt"; var file = new System.IO.FileInfo(path); using (var reader = new System.IO.StreamReader(file.OpenRead())) { String str = String.Empty; TableBatchOperation batchOperation = new TableBatchOperation(); int row = 0; string LastPartitionKey = string.Empty; while ((str = reader.ReadLine()) != null) { row++; if (row == 1)//第一行是标题不作为数据行 { continue; } var datas = str.Split(','); if (datas.Length < 11) { continue; } var BizEntity = new AzureWebSdkApp.Models.BizEntity() { PartitionKey = datas[1].Trim(), RowKey = datas[0].Trim(), TARMAGIC = datas[2].Trim(), CONTSEQ = datas[3].Trim(), CONTDATE = datas[4].Trim(), CONTCODE = datas[5].Trim(), CONTNOTE1 = datas[6].Trim(), CONTNOTE2 = datas[7].Trim(), CONTNOTE3 = datas[8].Trim(), FOLLDATE = datas[9].Trim(), FOLLCODE = datas[10].Trim(), }; if (BizEntity.PartitionKey == "") { continue; } //同一批的实体的PartitionKey必须一致 if ((!string.IsNullOrEmpty(LastPartitionKey) && BizEntity.PartitionKey != LastPartitionKey)) { //将之前的批处理数据执行完毕 if (batchOperation.Count > 0) { table.ExecuteBatch(batchOperation); batchOperation.Clear(); } LastPartitionKey = BizEntity.PartitionKey; } //批中的实体最多100个 if (batchOperation.Count < 100) { batchOperation.InsertOrReplace(BizEntity); LastPartitionKey = BizEntity.PartitionKey; } if (batchOperation.Count == 100) { table.ExecuteBatch(batchOperation); batchOperation.Clear(); } } if (batchOperation.Count > 0) { table.ExecuteBatch(batchOperation); batchOperation.Clear(); } } return RedirectToAction("Index"); }
上面的代码主要的工作就是读取含有数据的文本文件,将每行文字转为实体对象并满足批次上传的要求。我的逻辑没有优化,你可以优化更有效的提高上传性能。
这是一个非常令人激动的测试,测试的结果只有一个字:快!300万数据中,无论是查询RowKey,还是PartitionKey,还是PartitionKey和RowKey,还是其他非Key属性,或者Key和其他非Key属性混合查询,得到的体验就是快!
Table的查询由TableQuery实例化实体查询对象,由Table的ExecuteQuery方法具体执行。对查询的条件是通过一个查询表达字符串在TableQuery的Where中描述。为了简化Where中的查询表达式,SDK提供了TableQuery的一系列方法帮助我们构造查询字符串
<div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;">[HttpPost]</span></div><div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;">public ActionResult List(string name, FormCollection values)</span></div>{ ViewBag.TableName = name; <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> ViewBag.RowKey = values["rowkey"];</span></div> ViewBag.PartitionKey = values["partitionkey"]; <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> ViewBag.CONTCODE = values["contcode"];</span></div> ViewBag.CONTSEQ = values["contseq"]; <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> IEnumerable<AzureWebSdkApp.Models.BizEntity> result = null;</span></div> CloudTable table = tableClient.GetTableReference(name); <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> var filters = new List<string>();</span></div> var query = new TableQuery<AzureWebSdkApp.Models.BizEntity>(); <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> filters.Add(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, values["partitionkey"]));</span></div> if (!string.IsNullOrWhiteSpace(values["partitionkey"])) { } if (!string.IsNullOrWhiteSpace(values["rowkey"])) <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> if (!string.IsNullOrWhiteSpace(values["contseq"]))</span></div> { filters.Add(TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, values["rowkey"])); } { <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> if (!string.IsNullOrWhiteSpace(values["contcode"]))</span></div> filters.Add(TableQuery.GenerateFilterCondition("CONTSEQ", QueryComparisons.Equal, values["contseq"])); } { filters.Add(TableQuery.GenerateFilterCondition("CONTCODE", QueryComparisons.Equal, values["contcode"])); <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> result = table.ExecuteQuery(query);</span></div> } if (filters.Count > 0) { var filter = filters.Aggregate((item, next) => string.Format("({0}) and ({1})", item, next)); result = table.ExecuteQuery(query.Where(filter)); } else { } <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;">}</span></div><div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> return View(result);</span></div>
上述代码说明当需要多条件我们可以自己构造Where需要的查询字符串。
对应的View代码如下,其中你可以了解到上述代码中ViewBag是用来保存用户上次选择的内容以提供比较良好的体验。
@model IEnumerable<AzureWebSdkApp.Models.BizEntity> @{ ViewBag.Title = "List"; } <h2>List</h2> @using (Html.BeginRouteForm("default",new { name = @ViewBag.TableName },FormMethod.Post,new {@class="form-inline"})) { <div class="form-group"> <label for="">PartitionKey</label> <input type="text" class="form-control" name="partitionkey" value="@ViewBag.PartitionKey"> </div> <div class="form-group"> <label for="">RowKey</label> <input type="text" class="form-control" name="rowkey" value="@ViewBag.RowKey"> </div> <div class="form-group"> <label for="">CONTSEQ</label> <input type="text" class="form-control" name="contseq" value="@ViewBag.CONTSEQ"> </div> <div class="form-group"> <label for="">CONTCODE</label> <input type="text" class="form-control" name="contcode" value="@ViewBag.CONTCODE"> </div> <input type="submit" value="submit" class="btn btn-primary" /> } <ul class="nav nav-pills" role="tablist"> <li role="presentation" class="active"><span>查询耗时 <span class="badge">42</span></span></li> </ul> <table class="table col-md-12"> <tr> <th>PartitionKey</th> <th>RowKey</th> <th>TARMAGIC</th> <th>CONTSEQ</th> <th>CONTDATE</th> <th>CONTCODE</th> <th>CONTNOTE1</th> <th>CONTNOTE2</th> <th>CONTNOTE3</th> <th>FOLLDATE</th> <th>FOLLCODE</th> <th>CONTWHO</th> <th>CONTTEMP</th> </tr> @if (Model != null) { foreach (var item in Model) { <tr> <td>@item.PartitionKey</td> <td>@item.RowKey</td> <td>@item.TARMAGIC</td> <td>@item.CONTSEQ</td> <td>@item.CONTDATE</td> <td>@item.CONTCODE</td> <td>@item.CONTNOTE1</td> <td>@item.CONTNOTE2</td> <td>@item.CONTNOTE3</td> <td>@item.FOLLDATE</td> <td>@item.FOLLCODE</td> <td>@item.CONTWHO</td> <td>@item.CONTTEMP</td> </tr> } } </table>
运行中我们按不同的查询来观察性能,总数据行为300多万行。
按PartitionKey查询结果:
按RowKey查询结果:
按非Key值查询结果:
按PartitionKey组合非Key值查询结果:
按RowKey组合非Key值查询结果:
现在瓶颈在于呈现而不是查询阶段。如何提升呈现效率,我之后的章节会有描述。
public ActionResult Delete(string name)
{
tableClient.GetTableReference(name).Delete();
return RedirectToAction("Index");
}
王豫翔,上海致胜信息技术有限公司开发部经理,微软最有价值专家(Microsoft MVP)。曾在各种类型企业做编程技术工作,从代码工人到架构设计,从CS到BS,从静态语言到动态语言,从企业应用到移动互联网。最近3年主持实施了多个大型BI项目和Azure项目。