Azure Blobs  

Udostępnij na: Facebook

Autor: Piotr Zieliński

Opublikowano: 2011-01-19

Wprowadzenie

Azure Blobs są pojemnikami przeznaczonymi do przechowywania ogromnej ilości danych w formie plików (BLOB – Binary Large Object), które mogą mieć wielkość nawet kilkuset gigabajtów. Dzięki Azure uzyskujemy olbrzymią skalowalność. Na przykład pliki często używane są replikowane, dzięki czemu użytkownik nigdy nie doświadczy niskiej wydajności przejawiającej się długim czasem oczekiwania.

Struktura

Bloby umieszczone są w tzw. kontenerach. Aby zrozumieć istotę kontenerów, najłatwiej utożsamiać je z katalogami na dysku twardym. Jedną z ważnych cech kontenerów jest możliwość definiowania polityki bezpieczeństwa – np. kontener publiczny (z którego mogą korzystać anonimowi użytkownicy) lub kontener prywatny. Nie można jednak zagnieżdżać kontenerów tak, jak ma to miejsce w przypadku katalogów. Występują również dwa typy blobów: Block Blob i Page Blob. Pierwszy z nich składa się z bloków (każdy blok może mieć maksymalnie 4MB) identyfikowanych przez Block ID. Dzięki temu staje się możliwe równoległe wgrywanie plików lub zatrzymywanie i wznawianie podczas operacji wysyłania oraz ściągania pliku. Drugi, Page Block, składa się ze zbioru stron. Każda ze stron zawiera dane oraz jest identyfikowana za pomocą przesunięcia od początku bloba. Ze względu na architekturę proces zapisu wspomnianych blobów nie jest taki sam (dokładnie zostanie to przedstawione w dalszej części artykułu). Zarówno kontener, jak i blob mogą zawiera metadane w wielkości nie przekraczającej 8KB.

BLOB oraz REST API

Bloby, tak jak inne pojemniki w Azure, mają architekturę REST – co oznacza, że do danych odwołujemy się za pomocą wywołań HTTP. Zapytania do blobów mają następujący schemat:

http://nazwa_konta.blob.core.windows.net/nazwa_kontenera/nazwa_bloba

Oczywiście adres będzie nieco inny, jeśli wykorzystujemy lokalne środowisko symulacyjne. Reszta jednak pozostaje bez zmian.  Jeśli zatem chcemy odwołać się do pliku o nazwie „obrazek.jpg” znajdującego się w kontenerze „zdjecia”, powinniśmy wysłać następujący URL:

http://nazwa_konta.blob.core.windows.net/zdjecia/obrazek.jpg

Na szczęście Microsoft dostarcza biblioteki, które ułatwiają korzystanie z serwisu REST. Dzięki temu będziemy mogli operować na blobie za pomocą intuicyjnych klas.

Utworzenie kontenera

Jedną z najbardziej podstawowych operacji jest utworzenie kontenera („katalogu”), a pierwszym krokiem jest stworzenie obiektu reprezentującego konto Azure Storage:

StorageCredentialsAccountAndKey credentials = null;

credentials = new StorageCredentialsAccountAndKey("account_name", "key");

CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(credentials, true);

Dobrym podejściem jest przechowywanie danych w pliku konfiguracyjnym. Jeśli zdecydujemy się na ten krok, możemy wykorzystać statyczną funkcję CloudStorageAccount.Parse, która jako parametr przyjmuje string zawierający:

„AccountName=nazwa_konta;AccountKey=klucz;DefaultEndpointsProtocol=https”.

Dane te z kolei można przechowywać w sekcji appSettings (plik konfiguracyjny).

Następnie możemy zainicjalizować klienta BLOB:

CloudBlobClientblobClient=cloudStorageAccount.CreateCloudBlobClient();

W celu utworzenia kontenera wystarczy wywołać metodę Create na obiekcie CloudBlobContainer:

CloudBlobContainer container = new CloudBlobContainer("Zdjecia", blobClient);

container.Create();

Powyższy kod wyśle tak naprawdę zapytanie HTTP PUT. Można przekonać się o tym, np. wykorzystując bezpłatną aplikację Fiddler (http://www.fiddler2.com).

Kompletny kod źródłowy:

StorageCredentialsAccountAndKey credentials = null;

credentials = new StorageCredentialsAccountAndKey("account_name", "key");

CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(credentials, true);

CloudBlobClientblobClient=cloudStorageAccount.CreateCloudBlobClient();

CloudBlobContainer container = new CloudBlobContainer(containerName, blobClient);

container.Create();

W następnych fragmentach kodu tworzenie klienta blob (CloudBlobClient) będzie już pomijane.

Listing kontenerów

Aby uzyskać listing kontenerów, wystarczy wysłać zapytanie HTTP GET (zawierające oczywiście nagłówek uwierzytelniający) na adres o schemacie:

http://nazwa_konta.blob.core.windows.net/?comp=list

Łatwiej jednak wykorzystać do tego celu API:

IEnumerable<CloudBlobContainer> containers=blobClient.ListContainers();

foreach (CloudBlobContainer container in containers)

{

              Console.WriteLine(container.Name);                            

}

Metadane

Jak już wspomniano, kontenery i bloby mogą zawierać skojarzone ze sobą metadane (maksymalnie 8KB). W metadanych mogą zostać umieszczone reguły autoryzacyjne, hashe wykorzystywane przy sprawdzaniu integralności lub jakiekolwiek dane zdefiniowane przez programistę – metadane stanowią po prostu słownik z kluczem i wartością:

CloudBlobContainer container = new CloudBlobContainer("zdjecia", GetBlobClient());

container.Metadata.Add("klucz", "wartosc");

container.SetMetadata();

Operacje na prostych blobach

Zacznijmy od wysłania tekstu do prostego bloba:

CloudBlobContainer container = blobClient.GetContainerReference("zdjecia");

CloudBlob blob = container.GetBlobReference("hello.txt");

blob.UploadText("Hello word!");

Oprócz wysyłania tekstu można przekazać strumień danych, tablicę bajtów czy ścieżkę do pliku. Oto kilka sygnatur:

blob.UploadByteArray(byte[] bytes);

blob.UploadFile(string fileName);

blob.UploadFromStream(System.IO.Stream source);

Powyższe przykłady pozwolą wgrać dane jedynie nieprzekraczające 64 MB. Jeśli potrzebujemy wysłać większy plik, musimy podzielić dane na wspomniane na początku artykułu bloki (szczegóły w dalszej części).

Parametr ETag

Pobierając bloby, w nagłówku HTTP otrzymamy wartość ETag, która pozwala określić wersję pliku na serwerze. Dzięki temu po samym nagłówku jesteśmy w stanie stwierdzić, czy mamy aktualną wersję, czy jednak musimy pobierać dane. Przykład:

           

CloudBlobContainer container = GetBlobClient().GetContainerReference("zdjecia");

CloudBlob blob = container.GetBlobReference("hello.txt");

string blobData = blob.DownloadText();

string eTag = blob.Properties.ETag;

try

{

                blobData = blob.DownloadText(new BlobRequestOptions()

                {

                    AccessCondition = AccessCondition.IfNoneMatch(eTag)

                });

}

catch (StorageClientException ex)

{

                if (ex.StatusCode != System.Net.HttpStatusCode.NotModified)

                    throw;

                else

                {

                }

 }

Aby skopiować jeden blob do drugiego, nie trzeba wgrywać od nowa kopii. Wystarczy skorzystać z funkcji CopyFromBlob:

CloudBlob blob = container.GetBlobReference("hello.txt");

CloudBlobcopyBlob = container.GetBlobReference("hello-copy.txt");

copyBlob.CopyFromBlob(blob);

Oprócz klasycznych kopii istnieją tzw. snapshoty. Utworzenie ich jest szybsze, ale wiąże się z pewnymi ograniczeniami:

  • snapshoty są wyłącznie do odczytu,
  • nie można usunąć bloba, dopóki istnieje skojarzony z nim snapshot.

Modyfikacja na oryginalnym blobie nie powoduje zmian na snapshocie. Aby utworzyć snapshota, wystarczy wywołać metodę CreateSnapshot.

Listing blobów

Podstawowy listing blobów jest analogiczny do pobierania listy kontenerów, a mianowicie:

CloudBlobContainer container = GetBlobClient().GetContainerReference("zdjecia");

IEnumerable<IListBlobItem> items = container.ListBlobs();

Jeśli prześledzilibyśmy wysyłane zapytania HTTP, zobaczylibyśmy najpierw żądanie GET, a następnie serwer odpowiedziałby dokumentem XML przedstawiającym wszystkie bloby danego kontenera.

Na wstępie zostało powiedziane, że Azure Blobs nie wspiera zagnieżdżeń kontenerów tak jak to ma miejsce w strukturze katalogowej w Windows. Można jednak stworzyć „wirtualne” katalogi, nazywając odpowiednio bloby. Załóżmy, że w kontenerze „zdjecia” chcemy mieć podfolder „wakacje”. W takim przypadku musimy wysyłane bloby nazwać następująco:

  • wakacje/zdjecie1.jpg
  • wakacje/zdjecia2.jpg
  • zdjecie3.jpg

Wykorzystując powyższe nazewnictwo, zdjecie3.jpg zostanie wysłane do głównego kontenera („zdjecia”), a pozostałe dwa zostaną umieszczone w wirtualnym podfolderze „wakacje”. W c# do folderu „wakacje” możemy uzyskać dostęp za pomocą:

CloudBlobContainer container = GetBlobClient().GetContainerReference("zdjecia");

CloudBlobDirectorydirectory=container.GetDirectoryReference("wakacje/");

IEnumerable<IListBlobItem> items= directory.ListBlobs();

Przeszukując z kolei główny katalog “zdjecia”, należy ustawić parametr UseFlatBlobListing na true (w przeciwnym razie otrzymalibyśmy w zapytaniu również podfoldery, a chcemy przecież, aby funkcja zwróciła wyłącznie „zdjecie3.jpg”):

CloudBlobContainer container = GetBlobClient().GetContainerReference("zdjecia")

IEnumerable<IListBlobItem> items = container.ListBlobs(

                new BlobRequestOptions() { UseFlatBlobListing = true });

Wysyłanie dużych blobów

Każdy duży plik powinno się wysyłać w formie bloków. Wyobraźmy sobie plik o wielkości 2 GB. Wysłanie w jednym żądaniu  PUT tak dużego pliku mija się z celem – większość urządzeń sieciowych odrzuciłoby tak skonstruowany pakiet. Ponadto, wykorzystując bloki (maksymalny rozmiar 4MB), możemy wysyłać pliki równolegle. Pliki większe niż 64MB muszą być przesyłane w formie bloków, ponieważ Azure ich nie zaakceptuje. Wysyłanie danych w przedstawionej formie jest nieco trudniejsze:

CloudBlobContainer container = GetBlobClient().GetContainerReference("zdjecia");

CloudBlockBlob blob = container.GetBlobReference("duzy_plik.dat").ToBlockBlob;

List<string> ids=new List<string>();

for (int i = 0; i < 5; i++)

{

       ids.Add(Convert.ToBase64String(Guid.NewGuid().ToByteArray()));

       blob.PutBlock(ids.Last(), stream, null);

}

blob.PutBlockList(ids.ToArray());

Przede wszystkim każdy blok ma swój identyfikator. Za pomocą metody PutBlock wysyłamy blok (musimy dostarczyć identyfikator oraz strumień, z którego PutBlock ma czytać dane). Same wywołanie PutBlock nie spowoduje, że blob będzie gotowy do użycia. Należy jeszcze wywołać metodę PutBlockList, przekazując jej odpowiednią kolejność bloków (za pomocą listy identyfikatorów). Jeśli blob składa się z kilku takich samych bloków, można wysłać na serwer tylko raz podany blok, a następnie w liście identyfikatorów przesłać ID dwa razy.

Kompresja danych

Często istnieje potrzeba wysłania dużej ilości danych. W takiej sytuacji możemy wysłać dane w formie spakowanej, np. wykorzystując wbudowaną w .NET klasę GZipStream. Następnie należy ustawić ContentEncoding na gzip. Przeglądarki wspierają kompresję danych i po otrzymaniu bloba z ustawionym ContentEncoding na gzip są w stanie rozpakować plik. Często przedstawioną technikę stosuje się do skryptów JavaScript. Ustawienie ContentEncoding jest bardzo proste:

blob.UploadByteArray(compressedData.ToArray());

blob.Properties.ContentEncoding = "gzip";

blob.SetProperties();

Zakończenie

Azure Blob dostarcza skalowalne rozwiązanie do przechowywania dużej ilości danych. Użytkownicy Azure Blob nie muszą się martwić, gdzie dane są przechowywane oraz czy na dyskach twardych istnieje wystarczająca ilość wolnej przestrzeni. Dzięki Azure Blob programiści oraz użytkownicy absolutnie nie muszą zajmować się  warstwą sprzętową zaprojektowanego rozwiązania.


          

Piotr Zieliński

Absolwent informatyki o specjalizacji inżynieria oprogramowania Uniwersytetu Zielonogórskiego. Posiada szereg certyfikatów z technologii Microsoft (MCP, MCTS, MCPD). W 2011 roku wyróżniony nagrodą MVP w kategorii Visual C#. Aktualnie pracuje w General Electric pisząc oprogramowanie wykorzystywane w monitorowaniu transformatorów . Platformę .NET zna od wersji 1.1 – wcześniej wykorzystywał głównie MFC oraz C++ Builder. Interesuje się wieloma technologiami m.in. ASP.NET MVC, WPF, PRISM, WCF, WCF Data Services, WWF, Azure, Silverlight, WCF RIA Services, XNA, Entity Framework, nHibernate. Oprócz czystych technologii zajmuje się również wzorcami projektowymi, bezpieczeństwem aplikacji webowych i testowaniem oprogramowania od strony programisty. W wolnych chwilach prowadzi blog o .NET i tzw. patterns & practices.