Pull to refresh

Хранение данных в облаке Windows Azure

Reading time18 min
Views3.7K

Введение



Среди множества вариантов хранения данных, несомненно, можно выделить новую и интересную для изучения технологию хранения информации в облаке. На данный момент существует несколько сервисов по предоставлению услуг подобного вида, их можно сосчитать на пальцах – это Amazon Web Services, Sun Cloud, Windows Azure. Каждый из этих сервисов предоставляет свои интерфейсы для доступа к данным, в этой статье я бы хотел остановиться на сервисе Windows Azure.
Даная платформа облачных вычислений предоставляет 3 вида сервисов хранения информации:

  • Blob-сервис, позволяет хранить текстовую и бинарную информацию в специально организованных контейнерах- блобах.
  • Queue –сервис, позволяет организовывать неограниченное хранилище сообщений (по документации каждое сообщение может быть не более 8 кб).
  • Table-сервис, позволяет хранить структурированные в таблицы данные, доступ к которым производится через REST API.

Последний вышеперечисленный сервис – Table Storage, с первого взгляда, можно считать аналогом реляционного хранилища таблиц, однако, в его концепции есть некоторое количество важных различий, которые, по ходу статьи, я постараюсь раскрыть.
Данное хранилище может содержать таблицы (Tables), таблицы в концепции Table Storage – это коллекция сущностей(Entities), они подобны кортежам в реляционных хранилищах, в свою очередь сущность это набор определенных свойств(Properties), которые, в свою очередь, представляют пару «имя и типизированное значение» (name and typed value pair), сущности подобны полям в таблице в реляционных хранилищах. Тут следует отметить, что таблица в Table Storage, не задает структуру хранимых сущностей, а наоборот, хранимые сущности могут содержать различные свойства, но быть в одной таблице.
  1. Рассмотрим подробно, правила формирования имен таблиц:
  2. Имя таблицы могут содержать только буквенно-числовые знаки.
  3. Имя таблицы не может начинаться с числа.
  4. Имена таблиц чувствительны к регистру.
  5. Имена таблиц должны содержать от 3 до 63 знаков.
Теперь рассмотрим некоторые аспекты в формировании свойств и сущностей:
  • Имена свойств чувствительны к регистру и не могут содержать в себе более 255 знаков.
  • У каждой сущности количество определенных пользователем свойств не должно превышать 253.
  • Суммарный размер данных у каждой сущности должен быть не более 1 мб.
  • Каждая сущность должна содержать 3 системных свойства описанных ниже.

Системные свойства сущностей.


Для поддержки сбалансированной загрузки данных, таблицы в хранилище могут разбиваться на разделы (Partitions), так вот, чтобы однозначно сказать, какая сущность какому разделу принадлежит, она должна содержать свойство PartitionKey, которое однозначно идентифицирует раздел таблицы, в котором расположена данная сущность.
Другим обязательным свойством в каждой хранимой сущности служит свойство RowKey, которое уже идентифицирует конкретную сущность в определенном разделе. Описываемые свойства RowKey и PartitionKey образуют пару первичного ключа (Primary Key), каждой сущности и должны быть указаны при операциях вставки, удаления и обновления.
И последним, самым незначимым для разработчика, будет свойство – Timestamp, оно предназначено для внутренних нужд сервиса и хранит информацию о времени последней модификации сущности.
Как уже отмечалось выше, для доступа к данным в Table Storage существует REST API, однако, Microsoft предоставляет специальную библиотеку для доступа к данным под названием ADO.NET Data Services (в последней версией фреймворка 4.0 она была переименована в WCF Data Services). Эта библиотека предоставляет разработчику классы, для работы со схемами данных, сущностями и их свойствами. Как ее использовать будет показано ниже, на примере создания клиента к сервисам данных Windows Azure.

Создание тестовой модели данных


Теперь вооружившись первоначальными техническими данными, можно перейти к созданию демонстрационной модели данных для Azure Table Service. Но для начала нам необходимо подготовить среду для тестирования и создания клиентских приложений. Для этого мы должны скачать и установить аддон для Visual Studio и Windows Azure SDK: http://www.microsoft.com/downloads/details.aspx?FamilyID=5664019e-6860-4c33-9843-4eb40b297ab6&displaylang=en
После чего можно приступать непосредственно к программированию. Для этого создадим в среде Visual Studio 2010 простое консольное приложение под названием TableServiceExample:


И добавим ссылки на сборки System.Data.Services.Client, Microsoft.WindowsAzure.StorageClient:

т

Теперь настала очередь озвучить то, что планируется реализовать, а именно, необходимо будет создать хранилище для двух сущностей:
  1. Товар, должен содержать поля: Имя, Марка, цена, ссылка на категорию товара.
  2. Категория товара, должна содержать поля: идентификатор категории, название.
Сразу хочется оговориться, что Table Services в текущей реализации не поддерживают ссылки на другие объекты (говоря языком СУБД, нет средств для поддержания внешних ключей), поэтому вся логика связывания и целостности ложиться на конечное приложение.
Для отображения данных в объекты приложения, необходимо создать соответствующие классы, предварительно унаследовав их от TableServiceEntity. Сразу хочется обозначить несколько аспектов которые касаются создаваемых объектов: 1) Все сущности товаров будут храниться в одном разделе (PartitionKey), под названием «ProductPartition», а сущности видов в разделе «ProductKindPartition» 2) Идентификатором RowKey будет строковое представление Guidа, указываемого при создании объекта.
Исходный код класса «Товар»:
public class Product : TableServiceEntity
    {
    public const string PartitionName = "ProductPartition";

    public Product()
    {

    }

    private Product(string partitionKey, string rowKey)
      : base(partitionKey, rowKey)
    {

    }

    public static Product Create()
    {
      string rowKey = Guid.NewGuid().ToString();

      return new Product(PartitionName, rowKey);
    }

    public string Name { get; set; }

    public string Trademark { get; set; }

    public int Kind { get; set; }
  }

* This source code was highlighted with Source Code Highlighter.

И исходный код класса «Вид товара»:
public class ProductKind : TableServiceEntity
    {
    public const string PartitionName = "ProductKindPartition";

    public ProductKind()
    {

    }

    private ProductKind(string partitionKey, string rowKey)
      : base(partitionKey, rowKey)
    {

    }

    public static ProductKind Create()
    {
      string rowKey = Guid.NewGuid().ToString();

      return new ProductKind(PartitionName, rowKey);
    }

    public int Id { get; set; }

    public string Name { get; set; }
  }

* This source code was highlighted with Source Code Highlighter.


Чтобы создать необходимые таблицы для хранения вышеописанных сущностей, необходимо определить контекст данных по таким критериям:

1) Класс контекста должен быть унаследованным от TableServiceContext
2) Для каждой таблицы, необходимо определить свойство типа IQueryable, где DataItemType – тип сущностей, которые будут храниться в таблице.

Посмотрим на исходный код нашего контекста данных:
public class TestServiceDataContext : TableServiceContext
    {
    public TestServiceDataContext(string baseAddress, StorageCredentials credentials)
      : base(baseAddress, credentials)
    {

    }

    public const string ProductTableName = "Product";

    public const string ProductKindTableName = "ProductKind";

     public IQueryable<Product> Product
    {
      get { return CreateQuery<Product>( ProductTableName ); }
    }
    
    public IQueryable<ProductKind> ProductKind
    {
      get { return CreateQuery<ProductKind>( ProductKindTableName ); }
    }
  }

* This source code was highlighted with Source Code Highlighter.

Прежде чем развернуть в табличном сервисе нашу схему данных, необходимо познакомится с принципами аутентификации Windows Azure Data Services.

Настройка доступа к Table Services


Каждый запрос к сервисам табличных данных Azure, должен быть аутентифицированным, на практике это значит, что каждый запрос REST API содержит специально сформированную подпись. Вдаваться в подробности не будем, хотя если интересно можно почитать соответствующий раздел SDK, по причине того, что всю черновую работу возьмет на себя пакет ADO .NET Data Services для Azure Storage, нам нужно лишь правильно указать аутентификационные данные. Такими данными будут выступать имя аккаунта(Account) и ключ доступа(Shared Key).
Прежде чем что-то добавить в создаваемую программу, нам нужно запустить локальный эмулятор Storage на компьютере, сразу нужно оговориться, что для него дополнительно нужен установленный SQL Server любой редакции, включая Express. Эта программа входит в комплект поставки Windows Azure SDK и ее название Development Storage. Перед первым запуском приложение создаст и инициирует базу данных, в которую будет в дальнейшем использовать для хранения информации. Если все пройдет нормально, тогда в области уведомлений появится значок Development Storage и щелкнув по нему можно будет просмотреть какие из сервисов запущены на данный момент времени, а также их локальные адреса:

пп

Аутентификация в них происходит по хорошо известным и заранее сгенерированным данным:
  • Account name: devstoreaccount1
  • Account key: Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==

Именно эту информацию мы будем использовать в приложении. Для начала, добавим конфигурационный файл App.config и в секцию appSettings запишем следующие строки:
<configuration>
 <appSettings>
  <add key="AccountName" value="devstoreaccount1"/>
  <add key="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
  <add key="TableStorageEndpoint" value="http://127.0.0.1:10002/devstoreaccount1"/>  
 </appSettings>
</configuration>

* This source code was highlighted with Source Code Highlighter.

Теперь, чтобы верно оформить наш код, создадим класс TableStoreManager и добавим в него следующие поля:
  1. _credentials типа StorageCredentialsAccountAndKey – представляет собой данные по доступу к табличному хранилищу (Table Storage), через схему аутентификации по общему ключу (Shared Key)
  2. _tableStorage типа CloudTableClient — клинет для доступа к табличному хранилищу.
  3. _context типа TestServiceDataContext – класс созданный в предыдущем разделе с описанием всех таблиц.

После в описываемом типе TableStoreManager необходимо проинициализировать все эти поля такими строками:
string accountKey = ConfigurationManager.AppSettings["AccountSharedKey"];
string tableBaseUri = ConfigurationManager.AppSettings["TableStorageEndpoint"];
string accountName = ConfigurationManager.AppSettings["AccountName"];     

_credentials = new StorageCredentialsAccountAndKey(accountName, accountKey);
_tableStorage = new CloudTableClient(tableBaseUri, _credentials);
_context = new TestServiceDataContext(tableBaseUri, _credentials);

* This source code was highlighted with Source Code Highlighter.

Теперь имея все инициализированные компоненты доступа, необходимо осуществить проверку на необходимость создания таблиц и если это требуется создать их в хранилище, сделаем это в отдельном методе TryCreateSсhema:
var list = from table in _tableStorage.ListTables()
            where table == TestServiceDataContext.ProductKindTableName
            || table == TestServiceDataContext.ProductTableName select table;

  if (list != null && list.ToList<string>().Count == 0){
        CloudTableClient.CreateTablesFromModel(typeof(TestServiceDataContext),
          ConfigurationManager.AppSettings["TableStorageEndpoint"],
          _credentials);
}

* This source code was highlighted with Source Code Highlighter.

Здесь сначала идет выборка всех таблиц существующих таблиц, а также сравнение их имен с имеющимися в схеме, и если таблицы не будут обнаружены, тогда программа создаст их согласно той модели, что мы определили в классе TestServiceDataContext. И, на последок, необходимо добавить вызов созданного метода в метод Initialize, который был определен ранее.

Выборка данных из хранилища.


Azure Table Storage имеет довольно разнообразный API для создания запросов к таблицам в хранилище, так например можно запросить, некоторое количество элементов, или вернуть первый из них, а также навесить фильтрацию по определенным полям. Но тут следует заметить, что в текущей реализации рассматриваемого Storage имеются некоторые ограничения, во первых это то, что за один раз не может вернуться более тысячи элементов и тот факт, что каждый запрос не может выполняться дольше чем пять секунд, если хоть одно из этих условий не будет выполнено, тогда сервис вернет ошибку. Но вернемся к созданию запросов, ADO .NET Services Framework делает легче рутинную работу по использованию REST API к сервисам хранилища и предоставляет пользователю синтаксис LINQ для составления запросов. К сожалению, не все возможности LINQ для доступа к данным, в текущей реализации поддерживаются, сейчас можно пользоваться только несколькими операторами:
  1. From – поддерживается полностью
  2. Where – поддерживается полностью
  3. Take – поддерживается с ограничением максимума в 1000 элементов
  4. First, FirstOrDefault – поддерживается полностью.

На этом список заканчивается. Для составления запросов необходимо использовать метод TestServiceDataContext.CreateQuery, где T тип возвращаемой сущности.
Построим метод, который возвращает все имеющиеся товары (ограничение в 1000 элементов пока не проверяем)
public List<Product> GetAllProducts()
    {
var products = from product in _context.CreateQuery<Product>(TestServiceDataContext.ProductTableName) select product;
        return products.ToList<Product>();
    }

* This source code was highlighted with Source Code Highlighter.

Следом напишем выборку всех имеющихся категорий, а также определенной категории по ее идентификатору:
  public List<ProductKind> GetProductKinds()
    {
      var productKinds = from productKind in
                _context.CreateQuery<ProductKind>(TestServiceDataContext.ProductKindTableName)
              select productKind;
      return productKinds.ToList<ProductKind>();
    }

     public ProductKind GetProductKind(int id)
    {
      var productKinds = (from productKind in
                  _context.CreateQuery<ProductKind>(TestServiceDataContext.ProductKindTableName)
                where productKind.Id == id
                select productKind);

      var productKindList = productKinds.ToList<ProductKind>();

      ProductKind result = null;

      if (productKindList.Count > 0) {

        result = productKindList[0];
      }

      return result;
    }

* This source code was highlighted with Source Code Highlighter.

Теперь хотелось бы проверить наш сервис в работе, но если мы его запустим, и все сработает верно, то все равно ничего не увидим, так как в хранилище нет данных, поэтому для первой демонстрации нужно еще реализовать механизм добавления сущностей.

Добавление данных в облако


Процедура добавления новых сущностей с помощью ADO .NET Services выглядит довольно простой, достаточно лишь следить, чтобы добавляемые записи имели уникальные RowKey и PartiotionKey в пределах всей таблицы. Для операции вставки необходимо в метод созданного контекста TestServiceDataContext.AddObject передать первым параметром – имя таблицы, куда будет производиться добавления и вторым параметром – сам объект для добавления. После вызвать два метода – DataServiceContext.SaveChanges –для принудительной отправки новых данных в облачное хранилище, а также DataServiceContext.Detach – для удаления ссылок из контекста на объект. Таким образом, в классе TableStoreManager, мы создадим два метода Insert соответственно для добавления сущностей Product и ProductKind:
public void Insert(Product product)
    {
      _context.AddObject(TestServiceDataContext.ProductTableName, product);
      _context.SaveChanges();
      _context.Detach(product);
    }

    public void Insert(ProductKind productKind)
    {
      _context.AddObject(TestServiceDataContext.ProductKindTableName, productKind);
      _context.SaveChanges();
      _context.Detach(productKind);
    }

* This source code was highlighted with Source Code Highlighter.


Первое тестирование сервиса


После продолжительного знакомства с архитектурой Azure Table Storage, мы можем увидеть этот сервис в действии, для этого пропишем в методе Main, нашей консольной программы, следующие строки:
try
      {
        TableStoreManager manager = new TableStoreManager();
        manager.Initialize();

        ProductKind productKind = ProductKind.Create();
        productKind.Name = "Общий";
        productKind.Id = 1;
        manager.Insert(productKind);

        Product product = Product.Create();
        product.Name = "Пряники";
        product.Kind = productKind.Id;
        product.Trademark = "Candy";
        manager.Insert(product);
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }

* This source code was highlighted with Source Code Highlighter.

Этой программой мы, во-первых, инициализируем структуру Table Services, создавая там необходимые таблицы и, во-вторых, добавляем по одной записи в таблицы «Товаров» и «Виды Товаров». Теперь перед тестированием нужно не забыть запустить локальное «облако» DevelopmentStorage и выполнить нашу программу. Если все написано и сделано правильно, то программа подождет несколько секунд и вернет управление без единой ошибки. Теперь необходимо убедиться, что данные действительно сохранились в локальном хранилище, для этого изменим метод Main:
try
      {
        TableStoreManager manager = new TableStoreManager();
        manager.Initialize();

        var products = manager.GetAllProducts();

        foreach (Product product in products)
        {
          Console.WriteLine("Товар: {0} Марка {1}", product.Name, product.Trademark);

          ProductKind productKind = manager.GetProductKind(product.Kind);

          if (productKind != null)
          {
            Console.WriteLine("Вид товара: {0}", productKind.Name);
          }
        }
        
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }

* This source code was highlighted with Source Code Highlighter.

Снова исполним программу, не забывая про DevelopmentStorage, и если все верно, тогда в консоли мы увидим, что все сохраненные данные возвратились и готовы к обработке:

вв

Обновление сущностей


Механизм обновления сущностей в Table Storage довольно прост, каждый раз необходимо указывать значения RowKey и PatitionKey у изменяемого объекта. Для обеспечения конкурентного доступа к сущностям был введен обязательный флаг – Etag (хидер If-Match в REST API). Данный флаг определяет, когда тот или иной объект был изменен, и если данный Etag не совпадает с уже имеющимся хранилище не разрешит изменять данные, и вернет ошибку, сигнализирующую, что необходимо перезапросить информацию. Но можно самостоятельно повлиять на безусловное обновление установив в ETag символ *.
Чтобы обновить объект через Ado .Net Data Services, необходимо сначала добавить его в контекст методом TableServiceContext.AttachTo, после вызвать передав этот же объект в метод TableServiceContext.UpdateObject и отправить информацию на сервер вызовом метода TableServiceContext.SaveChanges:
public void Update(Product product)
{
       _context.Detach(product);
      _context.AttachTo(TestServiceDataContext.ProductTableName, product, "*");
      _context.UpdateObject(product);
      _context.SaveChanges();
}

* This source code was highlighted with Source Code Highlighter.

В самом начале перед обновлением необходимо убедиться, что обновляемый объект уже не в поле зрении Ado .NET Services, для этого просто вызываем метод TableServiceContext.Detach.
Теперь можно написать небольшой тест и убедиться, что все работает:
TableStoreManager manager = new TableStoreManager();
        manager.Initialize();
        var products = manager.GetAllProducts();
        foreach (Product product in products)
        {
          product.Name += " 2";
          product.Trademark = "";
          manager.Update(product);
        }
        products = manager.GetAllProducts();
        foreach (Product product in products)
        {
          Console.WriteLine(product.Name);

* This source code was highlighted with Source Code Highlighter.

Запустив написанный код, можно увидеть, что данные благополучно изменены, и тестирование созданной процедуры можно считать завершенной.

Удаление сущностей.


Подобно обновлению, удаление объектов требует наличия двух свойств – это RowKey и PartitionKey, а также подразумевает указание флага Etag – поведение которого, аналогично описанному в предыдущем разделе. Фактически при удалении сущность помечается как «удаленная» и становится недоступной для клиентов, а физически она удаляется, когда происходит периодическая сборка мусора. Код для удаления нашей сущности товар -Product с использованием ADO .NET Services приведен ниже:
public void Delete(Product product)
    {
      _context.Detach(product);
      _context.AttachTo(TestServiceDataContext.ProductTableName, product, "*");
      _context.DeleteObject(product);
      _context.SaveChanges();
    }

* This source code was highlighted with Source Code Highlighter.

Чтобы протестировать корректность работы, в методе Main можно написать следующие строки:
TableStoreManager manager = new TableStoreManager();
        manager.Initialize();
        var products = manager.GetAllProducts();

        foreach (Product product in products)
        {
          Console.WriteLine("Товар: {0} Марка {1}", product.Name, product.Trademark);
          manager.Delete(product);
        }

        products = manager.GetAllProducts();

        if (products.ToArray().Length == 0)
        {
          Console.WriteLine("Список пуст");
        }

* This source code was highlighted with Source Code Highlighter.

Заключение


В данной статье был показан общий подход по доступу к одному из видов облачных сервисов. Библиотека Ado .NET Services имеет ряд интересных нюансов, для рассмотрения которых можно написать еще одну статью, поэтому я здесь постарался не нагружать дополнительными условиями код, а привел его как можно в облегченном варианте.
К сожалению, полноценная версия платформы Azure, на данный момент недоступна для Российских потребителей, поэтому протестировать написанный пример не получится. Однако автор является обладателем с весны 2009 года инвайтом на CTP, который дает право в целях ознакомления с Azure создать некоторое количество (с ограничениями), на реальном облаке, сервисов. В связи с этим, данный подход был апробирован мною около года назад, путем создания облачного сервиса, который работает без остановок уже почти год (в июне 2009 он был запущен) — это говорит о действительной надежности облачных вычислений перед консервативными.

PS
Исходники можно скачать тут
Tags:
Hubs:
0
Comments2

Articles