Azure 基礎:Table storage

sparkdev發表於2017-02-25

Azure Storage 是微軟 Azure 雲提供的雲端儲存解決方案,當前支援的儲存型別有 Blob、Queue、File 和 Table。其中的 Table 就是本文的主角 Azure Table storage。

Azure Table storage 是一個在雲端儲存結構化 NoSQL 資料的服務。它不僅存取速度快,而且效費比高。MSDN 上的說法是:成本顯著低於傳統 SQL!
筆者最近在專案中用 Table storage 實現了一個日誌表,在此和大家分享一下 Table storage 的基本用法。

Azure storage account

就概念上來講,Table storage 只是 Azure 提供的儲存服務的一種。其他的儲存服務還有 Blob、Queue、File 等。對這些儲存服務的訪問控制都是透過 storage account 來進行的。所以要想使用 Table storage 需要先建立你的 storage account。具體建立過程不是本文重點,請參考 MSDN。但你需要去了解一下 Access keys,它就是你訪問 storage account 的使用者名稱和密碼:

建立 Table storage 的物件

在使用 Azure Table storage 的相關物件前,我們需要安裝對應的包。其實很簡單,只需在 Visual Studio 的 Package Manager Console 中輸入:

Install-Package WindowsAzure.Storage

Visual Studio 會自動安裝 WindowsAzure.Storage 包及其依賴的所有包,安裝完成後的 packages.config 檔案看起來像這個樣子:

安裝完相關的包以後,我們就可以使用其中的型別了。

CloudStorageAccount 類表示一個 Azure storage account,我們得先建立它的例項才能訪問屬於它的資源。

// 注意連線字串中的 xxx 和 yyy,分別對應 Access keys 中的 Storage account name 和 key。
CloudStorageAccount storageAccount = CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=xxx;AccountKey=yyy");

// CloudTableClient 類是 Windows Azure Table Service 客戶端的邏輯表示。我們使用它來配置和執行對 Table storage 的操作。
CloudTableClient cloudTableClient = storageAccount.CreateCloudTableClient();

CloudTable 類表示一張資料表。

// 建立一個例項去引用 Table storage 中的一張表,我們測試用的表名叫 "MyLogTable"。
CloudTable logTable = cloudTableClient.GetTableReference("MyLogTable");
// 如果不確定表是否被建立過,可以呼叫 CreateIfNotExists 方法。
logTable.CreateIfNotExists();

這樣在後面的操作中就可以確保 MyLogTable 表是存在的。

有了 logTable 物件我們就可以向表中插入資料了。但是等等,好像少了點什麼。我們開篇第一句中就說明了,Table storage 儲存的是結構化的資料,所以我們還要先定義儲存的資料的型別。

定義日誌類

在定義我們自己的資料型別時,有一個強制性的要求,必須繼承自 TableEntity 型別:

internal class MyLogEntity : TableEntity
{
    public MyLogEntity() { }
    public MyLogEntity(string pkey, string rkey)
    {
        this.PartitionKey = pkey;
        this.RowKey = rkey;
    }

    public DateTime LogDate { get; set; }
    public string LogMessage { get; set; }
    public string ErrorType { get; set; }
}

在我們的設計中,PartitionKey 用來存放產生日誌的年份和月份(例如201607),RowKey 用來存放產生日誌的天和時分秒毫秒(例如160934248492)。日誌資料主要是 LogDate,LogMessage 和 ErrorType。

把資料插入到 Table storage

終於可以向表中插入資料了,試一下先:

DateTime now = DateTime.Now;
string partitionKey = now.ToString("yyyyMM");
string rowKey = now.ToString("ddHHmmssffff");
MyLogEntity logEntity = new MyLogEntity(partitionKey, rowKey);
logEntity.LogDate = now;
logEntity.LogMessage = "test message";
logEntity.ErrorType = "error";
// TableOperation 類表示對一個表進行的操作,可以插入一行或多行資料,刪除資料,更新資料等。
TableOperation insertOperation = TableOperation.Insert(logEntity);
logTable.Execute(insertOperation);

看起來還不錯,我們用 Visual Studio 自帶的 Cloud Explorer 檢視一下 MyLogTable 中的內容:

OK,資料已經成功插入到 MyLogTable 表中。接下來我們看看如何批次的插入資料。

TableBatchOperation batchOperation = new TableBatchOperation();
for (int i = 0; i < 10; i++)
{
    DateTime now = DateTime.Now;
    string partitionKey = now.ToString("yyyyMM");
    string rowKey = now.ToString("ddHHmmssffff");
    MyLogEntity logEntity = new MyLogEntity(partitionKey, rowKey);
    logEntity.LogDate = now;
    logEntity.LogMessage = "test message" + i.ToString();
    logEntity.ErrorType = (i%2) == 0 ? "error" : "warning";
    batchOperation.Insert(logEntity);
    Thread.Sleep(10);
}
logTable.ExecuteBatch(batchOperation);

這次我們把 TableOperation 類換成了 TableBatchOperation 類,然後一次插入十條資料。去檢查一下結果,OK 十條資料全部插入成功!
下面讓我們把迴圈中的 10 改成 200 試試:

怎麼收到一個 InvalidOperationException 呢?看看紅框中的內容,原來批次操作是有一些限制的:
1.    每個批次操作的資料上限是 100 條記錄。
2.    每個批次操作中的資料都必須保持相同的 partition key。
請大家在使用批次操作時務必注意這些限制條件!

查詢操作

對於日誌資料的操作,最重要的就是查詢。我們透過幾個具體的用例來介紹 Table storage 的查詢操作。

查詢所有的記錄

這是最簡單的查詢方法,一般是想要匯出全部資料時才會這麼幹:

TableQuery<MyLogEntity> query = new TableQuery<MyLogEntity>();
foreach (MyLogEntity entity in logTable.ExecuteQuery(query))
{
    Console.WriteLine("{0}\t{1}\t{2}\t{3}", entity.PartitionKey, entity.RowKey, entity.LogMessage, entity.ErrorType);
}

查詢某年的某個月的記錄

要查詢某個月的所有記錄也是比較容易的,因為我們設計的 PartitionKey 就代表了某個月份:

TableQuery<MyLogEntity> query = new TableQuery<MyLogEntity>().Where(
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "201607"));
foreach (MyLogEntity entity in logTable.ExecuteQuery(query))
{
    //...
}

請注意 TableQuery.GenerateFilterCondition 方法,我們建立了一個過濾條件:PartitionKey 等於 "201607"。這個查詢會把所有 PartitionKey 為 "201607" 的記錄都找到!

查詢某一條記錄

如果我們已經知道了一條記錄的 PartitionKey 和 RowKey,就可以透過這兩個條件直接查詢到這條記錄的詳情:

TableQuery<MyLogEntity> query = new TableQuery<MyLogEntity>().Where(
    TableQuery.CombineFilters(
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "201607"),
        TableOperators.And,
        TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "161148372454")));
foreach (MyLogEntity entity in logTable.ExecuteQuery(query))
{
    //...
}

這次我們使用了組合條件。雖然這裡只使用了條件運算操作 TableOperators.And 和 QueryComparisons.Equal,你完全可以嘗試其它的條件型別。唯一要注意的是:對於 PartitionKey 和 RowKey,QueryComparisons 的操作物件都是字串。

我們還需要更多的查詢條件,比如查詢某一天產生的所有日誌。在 MyLogTable 表中,這需要查詢以 "xx" 字串開頭的 RowKey。我會單獨在一篇文章中和大家分享相關內容,因為它並不像看起來的那麼簡單。
接下來我們介紹如何更新和刪除日誌表中的資料,當然這麼做並不恰當,我們這裡只是借用日誌表介紹更新和刪除操作而已。

更新記錄

TableOperation retrieveOperation = TableOperation.Retrieve<MyLogEntity>("201607", "161148372454");
TableResult retrievedResult = logTable.Execute(retrieveOperation);
MyLogEntity updateEntity = (MyLogEntity)retrievedResult.Result;

if (updateEntity != null)
{
    updateEntity.LogMessage = "new log message";
    TableOperation updateOperation = TableOperation.Replace(updateEntity);
    logTable.Execute(updateOperation);
}

這次我們先用 TableOperation.Retrieve 方法獲得一條資料的詳情,然後更新它的 LogMessage 屬性,最後使用 TableOperation.Replace 方法把新的內容更新的到 Table storage 中。

刪除記錄

實際上刪除一條記錄和更新一條記錄一樣麻煩,不同點是把 TableOperation.Replace 方法換成 TableOperation.Delete 方法:

TableOperation retrieveOperation = TableOperation.Retrieve<MyLogEntity>("201607", "161148372454");
TableResult retrievedResult = logTable.Execute(retrieveOperation);
MyLogEntity deleteEntity = (MyLogEntity)retrievedResult.Result;
if (deleteEntity != null)
{
    TableOperation deleteOperation = TableOperation.Delete(deleteEntity);
    logTable.Execute(deleteOperation);
}

刪除表

刪除表和建立表一樣簡單(可比刪除一條記錄容易多了):

logTable.DeleteIfExists();

總結

本文透過對一個日誌表的操作介紹了 Azure Table storage 的一個典型應用場景和基本的使用方法。從操作的程式碼上看和傳統的 sql 表操作差別還是挺大的。希望對朋友們瞭解 Azure Table storage 能有所幫助。

相關文章