daron yöndem | Microsoft Regional Director | Silverlight MVP
Microsoft Regional Director | Nokia Developer Champion | Azure MVP

Dün Microsoft'un organizasyonunda "Cloud DevCamp" gerçekleştirildi. Ben de gün boyunca üç oturum ile Compute Services, Mobile Services ve Azure Web Sites anlattım.

Malum bu bir community etkinliği değildi :) özellikle belirtiyim istiyorum çünkü kafalar karışabiliyor. Microsoft'un hafta içi sadece kurumsal katılım için organize ettiği bir etkinlikti. Ben de sizlerle twitter ve facebook üzerinden paylaşmıştım öncesinde.

Etkinlik community etkinliğinden öte Microsoft'un bir etkinliği olunca yukarıdaki gibi tweetler de kaçınılmaz oldu :) Neredeee.... community etkinliğinde açık büfe görmek pek mümkün değil tabi :)

Etkinliğe katılan herkese çok teşekkürler.

Görüşmek üzere!

Geçen hafta sonu Eskişehir Osmangazi Üniversitesi'ndeydim. "Cloud Development 101" adını koyduğum sıfırdan Cloud Nedir gibi konseptleri anlatan ve içerisinde de Microsoft'un Azure ile yaptıklarını gösteren bir sunum yaptım. İtiraf etmek gerekirse günübirlik bir etkinlik için 10 saat yolculuk son birkaç yıldır yapmadığım bir şeydi ve yıllar sonrasında "yılların etkisini" görmemi sağladı. Sözün özü... yaşlanmışım :) Şaka zannederlere sesleniyor... "10 yıl sonra görüşürüz."

Tüm yorgunluğa rağmen :) benim için çok eğlenceli bir etkinlikti. Etkinlikte emeği geçen tüm arkadaşlarla onlarca teşekkürler. Gösterdikleri hassasiyet takdir edilesi seviyelerdeydi. Bu gibi üniversite etkinliklerine katılan öğrenci kardeşlerim :) genelde maalesef farkında olmuyorlar fakat etkinliklere gönüllü olarak katılan benim gibi konuşmacıların emeklerinin yanı sıra etkinliğin organizasyonunda çalışan arkadaşlarının da inanılmaz katkısı var. Emin olun, en basiti bile olsa, bir organizasyonu yapmak ve alnının akı ile işin içinden çıkmak hiç kolay değil.

Bir dahakine görüşmek üzere Eskişehir! ;)

Bu hafta içi (dün) Konya, Karatay Üniversitesi'ndeydim. Her zamankinden farklı olarak teknik bir oturum yerine ilk defa aylar önce yine Konya'da sunduğum "Boş durma, boşa çalış." adındaki oturumumu sundum. Bu oturum özellikle benim "Uykudan Önce Bir Doz" ile paylaşmaya çalıştıklarımla aynı yönde bir paylaşım diyebiliriz.

Tabi ki dönüşte gençlerle bir etli ekmek yemeyi de unutmadık :) Nitekim eski performansımı gösteremiyorum artık ama olsun :) Benim için süper eğlenceliydi. Umarım katılan herkes için de faydalı olmuştur. Etkinliğin organizasyonunda görev alan tüm arkadaşlara da ayrıca çok teşekkürler. Elinize sağlık.

Görüşmek üzere!

Bloblarla ilgili birçok detayı daha önceki yazılarda inceledik. Hatta PageBlob ile BlockBlob arasındaki farklılığa da değinmiştik. Şu ana kadar bildiklerimizin üzerinden geçmek gerekirse PageBlob'ların 512 byte'lık paketler (max4MB'lık paket olabilir) şekilde veri saklayabildiğini ve daha ağırlıklı olarak random read/write için uygun olduklarını BlockBlob'ların ise daha büyük paketlerle çalışabildiklerini (4MB gibi MAX 64MB Paket olabilir, Storage Client'taki maksimum destek 32MB'a kadar.) ve ufaktan da bir transaction yapılarının olduğunu biliyoruz. PageBlob'lar 1TB'a kadar veri saklayabilirken BlockBlob'lar 200GB ile sınırlı. Gelin şimdi biraz daha detaylara girerek işlemlerin daha alt seviyelerde nasıl yapılabildiğince göz atalım.

Bir BlockBlob'ın kullanımı

Normal şartlarda StorageClient'ın yüksek seviyeli diyebileceğimiz API'lerini kullandığınızda PageBlob veya BlockBlob gibi ayrımlarla uğraşmazsınız ama eğer isterseniz birazdan yapacağımız gibi daha alt seviyeli API'lere doğru ilerleyerek tüm kontrolü ele almanız da mümkün.

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                     { PublicAccess = BlobContainerPublicAccessType.Blob });

İlk olarak yukarıdaki giriş kodumuz ile kendimize sıfırdan bir container yaratalım. Container'ımıza blob seviyesinde public access de verelim ki herkes ulaşabilsin. Şimdi ise yavaş yavaş bir BlockBlob yaratarak kullanımına bakalım.

[C#]

CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");

Blockblobları yaratırken CloudBlockBlob nesnesini kullanıyoruz. Genel mantık tek tek bloklar yüklemekten oluşuyor. Elinizdeki datayı istediğiniz boyutlarda bloklara bölerek bu blobları ayrı ayrı sunucuya gönderiyorsunuz. Her gönderdiğiniz bloğun bir de tabi ki blockID'si oluyor. İsterseniz bu blokları asenkron olarak gönderebilirsiniz, sırasız gönderebilirsiniz. Yani özetle kafanıza göre takılabilirsiniz :)

[C#]

List<string> blockIds = new List<string>();
for (int i = 0; i < 10; i++)
{
    var newId = Convert.ToBase64String(Encoding.Default.GetBytes(i.ToString()));
    blob.PutBlock(newId, new MemoryStream(Encoding.Default.GetBytes("TEST")), null);
    blockIds.Add(newId);
}

Yukarıdaki örnek kodda da görebileceğiniz üzere her bir Block'u PutBlock ile sunucuya gönderiyoruz. PutBlock'un toplam üç parametresi var, bunlardan ilki söz konusu bloğa ait ID bilgisi. ID'nin kesinlikle Base64 olması gerekiyor. İkinci parametre datamaızın ta kendisi. Ben deneme amaçlı bir çalışma için basit bir metin gönderiyorum ama siz elinizdeki büyük dosyaları da bloklara bölüp ayrı ayrı gönderebilirsiniz. Hatta multithread takılıp aynı anda birden çok blok bile gönderebilirsiniz. PutBlock'un son parametresi normalde verinin bir MD5 Hashi oluyor. Ben tembellikten eklemedim :)

Tüm bu işlemler sürerken BlockID'lerini bir listede tutuyoruz. Bunun nedeni ise aslında PutBlock dediğimizde verinin doğrudan Blob'a yansımıyor olması. Tüm blokları gönderdikten sonra gönderdiğiniz blokların ID'lerini ayrı bir liste olarak gönderip tüm block'ların commit olmasını sağlıyorsunuz.

[C#]

blob.PutBlockList(blockIds);

Böylece upload işlemi tamamlanmış oluyor. İşte bir BlockBlob'un çalışma yapısı bu şekilde. Aşağıdaki kodu bir bütün olarak çalıştırıp sonuca bir göz atın.

[C#]

protected void Page_Load(object sender, EventArgs e)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    var anaContainer = blobClient.GetContainerReference("anacontainer");
    anaContainer.CreateIfNotExists();
    anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });
    CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");
    List<string> blockIds = new List<string>();
    for (int i = 0; i < 10; i++)
    {
        var newId = Convert.ToBase64String(Encoding.Default.GetBytes(i.ToString()));
        blob.PutBlock(newId, new MemoryStream(Encoding.Default.GetBytes("TEST")), null);
        blockIds.Add(newId);
    }
    blob.PutBlockList(blockIds);
Response.Write(blob.Uri.ToString()); }

Ekrana yazdırılan adrese tarayıcıdan gittiğinizde doğal olarak arka arkaya yazılmış 10 tane TEST kelimesi olan bir metin dosyası ile karşılacaksınız.

Eğer isterseniz bir BlockBlob'un istediğiniz bir blokunu değiştirebilir veya üzerine yeni bloklar da ekleyebilirsiniz. Sonrasında commit ettiğiniz sürece dosyada değişikler anında gözükecektir. İşin güzel tarafı SnapShot almışsanız ve sadece tek bir block değiştirmişseniz sadece değiştirdiğiniz block'un boyutu içi para ödüyorsunuz ;)

[C#]

blockIds.AddRange(blob.DownloadBlockList(BlockListingFilter.Committed)
                      .Select(b => b.Name));
blob.PutBlock(blockIds[2], new MemoryStream(Encoding.Default.GetBytes("TEST2")), null);
blob.PutBlockList(blockIds);

Kodumuzun ilk satırında önemli bir detay var. DownloadBlockList(BlockListingFilter.Committed) dediğinizde commit edilmiş tüm blockların ID'lerini alabiliyorsunuz. Aynı şekilde UnCommitted bloklara da ulaşabilir ve commit edilmeden yine üzerlerinde değişiklik yapabilirsiniz. Bir Storage Account'ta toplam 100.000 uncommitted block olabilir ve toplam veri miktarı da 400GB'ı geçemez. Şimdilik örnek olması amacı ile ben gelen listeden ikinci block'u seçip metni değiştiriyorum. Sonunda ise tekrar blockID'leri gönderiyorum ki dosya son haline toparlansın.

Kodun son hali şu şekilde;

[C#]

protected void Page_Load(object sender, EventArgs e)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    var anaContainer = blobClient.GetContainerReference("anacontainer");
    anaContainer.CreateIfNotExists();
    anaContainer.SetPermissions(new BlobContainerPermissions()
                     { PublicAccess = BlobContainerPublicAccessType.Blob });
    CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");
    List<string> blockIds = new List<string>();
    blockIds.AddRange(blob.DownloadBlockList(BlockListingFilter.Committed)
                          .Select(b => b.Name));
    blob.PutBlock(blockIds[2], new MemoryStream(Encoding.Default.GetBytes("TEST2")), null);
    blob.PutBlockList(blockIds);
    Response.Write(blob.Uri.ToString());
}

Unutmadan, eğer bir bloğu bir hafta boyunca commit etmezseniz söz konusu block otomatik olarak silinecektir. Aynı şekilde upload edilmiş bir Block'un ID'si eğer bir PutBlockList listesinde bulunmazsa hemen PutBlockList komutu sonrasında listede bulunmayan ve blob'a ait diğer havada kalan blocklar silinir.

PageBlob'lara bakış...

PageBlob'lar daha nokta atışı çalışmaya yönelik. Ayrıca önce veriyi veriyim sonra beraber commit ederim gibi bir durum da yok. Yapılan her değişiklik anında sonuca yansıyor. İşin kötü tarafı ise bloblara 512 byte'ın katları şeklinde veri sakalamak zorunda olmamız :) Yani elinizde stream varsa ona göre ilerlemeniz gerekecek.

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });

İlk olarak yukarıdaki gibi klasik işlemlerimizi yapalım ve PageBlob'umuzu yaratmaya hazırlanmış olalım.

[C#]

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt")

Yukarıdaki kodu sanırım tahmin etmişsinizdir. Güzel bir PageBlob yaratarak yolumuza devam ediyoruz. Bundan sonrasında örnek olarak ben diskten bir TXT dosyası okuyup onu göndereceğimiz PageBlobumuza.

[C#]

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt");

byte[] buffer;
FileStream fileStream = new FileStream(@"C:\test.txt", FileMode.Open, FileAccess.Read);
try
{
    int length = (int)fileStream.Length;  
    buffer = new byte[512];
    int count;                            
    int sum = 0;
    //BURAYA BİRŞEYLER GELECEK       
}
finally
{
    fileStream.Close();
}

İlk olarak gelin yukarıdaki koda hızlıca bir bakalım. PageBlob'umuzu yaratmışız. Basit bir şekilde diskten bir dosyayı FileStream ile açmışız. Sonra da bazı değişkenlerimizi tanımlamışız. Hazırlık belli ki bir While ile stream'i okumak için yapılmış :) Önemli noktalardan biri buffer'ımızın 512 byte olması. Sürekli 512 byte'lık paketler göndereceğiz PageBlob'a. PageBlob'a gönderebileceğiniz paketler 512 byte ile 4MB arası değişebilir ama kesinlikle 512'nin katı olmak zorundalar.

[C#]

var EnYakin512Kati = ((length + 511) / 512) * 512;
pageBlob.Create(EnYakin512Kati);

while ((count = fileStream.Read(buffer, 0, 512)) > 0)
{
    pageBlob.WritePages(new MemoryStream(buffer), sum);
    buffer = new byte[512];
    sum += count; 
}

Bir üstteki kodun orta yerine yukarıdakini alıyoruz :) İlk olarak elimizdeki dosyanın stream uzunluğuna en yakın 512'nin katını bularak o boyuta denk gelecek şekilde pageBlob'un Create metodu ile Blob'u gerçekten yaratıyoruz :) Sonra bir while ile buffer'ımızı doldurup doldurup WritePages ile de bloba aktarıyoruz.

Kodun tamamını aşağıda bulabilirsiniz;

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt");

byte[] buffer;
FileStream fileStream = new FileStream(@"C:\test.txt", FileMode.Open, FileAccess.Read);
try
{
    int length = (int)fileStream.Length;  
    buffer = new byte[512];
    int count;                            
    int sum = 0;
    var EnYakin512Kati = ((length + 511) / 512) * 512;
    pageBlob.Create(EnYakin512Kati);
    while ((count = fileStream.Read(buffer, 0, 512)) > 0)
    {
        pageBlob.WritePages(new MemoryStream(buffer), sum);
        buffer = new byte[512];
        sum += count; 
    }       
}
finally
{
    fileStream.Close();
}
Response.Redirect(pageBlob.Uri.ToString());

İtiraf etmem lazım PageBlob'larla çalışma hiç kolay değil. En azından ben BlockBlob'ları daha çok seviyorum :) PageBlob'lar ise tek tek 512bytelık aralıklara kadar müdahale sağlıyor. PageBlob'lardan snapshot aldığınızda sadece değişen 512 byte'lar için para ödeyeceğini de hatırlamakta fayda var ;)

Kendinize çok iyi bakın.

Azure SDK'yi yüklediğinizde makinenize iki farklı emülatör yükleniyor. Bunlardan biri DevFabric de denilen Compute Emulator, yani uygulamanıza Azure ortamını yaşatan arkadaş, diğeri ise Storage Emulator, Azure Storage servislerini lokalde test etmemizi sağlayan araç. Her iki araca da Visual Studio ile ilk projenizi F5'e basarak çalıştırdıktan sonra "notification bar" dan ulaşabilirsiniz.

Azure Emülatörleri İş
Başında Azure Emülatörleri İş Başında

Storage Emulator

Storage Emulator localde sadece bir storage account sağlayabildiği için projeler arası çakışmalara neden olabilir. Özellikle aynı isimler container'lar kullanıyorsanız farklı datalar farklı projelerde kendini göstermeye başlayabilir :) Bu durumu engellemek adına Storage Emülatör'ün arayüzünü açıp tüm storage'ı resetleyebilirsiniz.

Storage Emulator'ü gidiyor... yerine command line geliyor... Storage Emulator'ü gidiyor... yerine command line geliyor...

"Herkes gider mersine biz gideriz tersine." sözünü hatırlatan kıvamda :) Azure Emulatorü'nün UI sürümünü Microsoft artık geliştirmeyeceğini söylüyor. Scripting desteği verebilmek açısından command line araçlarının gelmesi önemli fakat bu durum "UI'dan vaz geçme"yi de gerektirmez bence. Neyse, kişisel yorumlarımı bir kenara koyalım :) "Yeni command line" arayüzünü (şaka gibi) açtıktan sonra karşınıza kullanabileceğiniz komutların bir listesi geliyor.

Storage Emülatörü komutları karşımızda. Storage Emülatörü komutları karşımızda.

Storage Emulator içerisinde toplam üç storage servisi (Blob, Queue, Table) için gerekli endpointler yaratılmış durumda.

Servislerin local endpointleri. Servislerin local endpointleri.

Bu Endpoint'lere ister doğrudan REST Call'larla gidin ister StorageClient ile gidin bire bir Azure ortamındaki Storage servisleri ile aynı tepkileri vermekle yükümlüler ve itiraf etmek gerekirse şu ana kadar herhangi bir yanlışlarını görmedim :)

Arkada neler oluyor...

Esas işin ilginç tarafı şimdi başlayacak :) Storage Emulator SQL'e bağımlı. Bunun nedeni tüm datayı aslında SQL'de tutuyor olması. Burada çok önemli bir detay var; Storage Emulator'ın datayı SQL'de tutuyor olması Azure ortamında da aynı şeyin yapıldığı anlamına gelmiyor. Hatta açıklamalara göre Azure'daki Storage servislerinin arkasında SQL yok (Zaten mantıksız olurdu) ama arkada ne var diye sorarsanız :) bilmiyorum :)

Lokal yüklemede emülatörün arkasında SQL olması işimizi özellikle debuggine senaryolarında kolaylaştırabiliyor. Ne kadar ki üretkenliğinizi yükseltmek adına Storage Explorer tadında araçlarla çalışmak zorunda kalacak olsanız da :) işin dibine inmek isterseniz aslında basit bir şekilde SQL Management Studio ile bağlanıp emülatörün kullandığı veritabanını da inceleyebilirsiniz.

Storage Emülatörü arkasında SQL'e bir
bakış. Storage Emülatörü arkasında SQL'e bir bakış.

Yukarıdaki ekran görüntüsünde basit bir şekilde BlobContainer listesini görebiliyorsunuz. Bunun gibi tüm Storage verisini local emülatör SQL'de tutuyor. Acaba yolladıklarım kaydoluyor mu gibi testleri buralarda yapmak mümkün. Tabi yine tekrar etmem gerek; çok alt seviye bir testing aracı olarak düşünmek lazım bu yöntemi.

Azure Emulator

Gelelim bir de işin Azure Emulator tarafına. Azure Emulator bilgisayarınızdaki IIS Express'i kullanarak Web Role emülasyonu yapıyor. Instance'larınızı ve konsollarını tek tek emülatör arayüzünden görebiliyorsunuz. Bir anlamda local bir FC rolü oynamaya çalışıyor.

Azure Emulator
UI Azure Emulator UI

Yukarıdaki ekran görüntüsünde gördüğünüz deployment'ta sadece bir instance web role var. Buradaki rollerin her birine bağlanıp durumlarını takip edebiliyoruz. Ayrıca buradan instance'ları suspend edebilir farklı durumlara verdikleri tepkileri de ölçebilirsiniz.

Local emülatörün
parçaları... Local emülatörün parçaları...

Yukarıda kabaca local Azure Emülatörünün mimari olarak nasıl da gerçek Azure ortamına benzetilmeye çalışdığını görebilirsiniz. IISConfigurator'dan tutun WaIISHost'larımıza kadar herşey neredeyse aynı. Her Instance için bir HostBootStrapper var ve instance tipine göre de WaWorkerHost veya WaIISHost kullanılarak uygulama ayağa kaldırılıyor. Böylece Azure ortamına çok benzeyen bir debugging deneyimi yaşayabiliyoruz.

Hepinize kolay gelsin.

Blobların en sevdiğim özelliklerinden biri blob başına snapshot alabiliyor olmamız :) Sınır yok, istediğiniz kadar snapshot alabilirsiniz :) (yeter ki parasını ödeyin) Şaka bir yana SnapShot almanın kolaylığı ve "yedekleme" amaçlı olarak süper faydalı olması geliştireceğiniz uygulamalarda eminim ki ciddi katma değer sağlayacaktır.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

CloudBlockBlob blob = container.GetBlockBlobReference("istanbul_azure_logo.png");
var snapshotBlob = blob.CreateSnapshot();

Response.Write(snapshotBlob.SnapshotQualifiedUri);

Yukarıdaki kod içerisinde basit bir blob bulduktan sonra hemen üzerinden CreateSnapshot metodunu çağırıyoruz. Metodu çağırdığımız anda aslında artık blobun snapshot'ı yani bir kopyası tarihin tozlu raflarında yerini almış oluyor :) Blobun snapshot'ına direk ulaşmak isterseniz ancak SnapshotTime üzerinden snapshot'ın alındığı tarihi kullanarak ilerleyebiliyorsunuz. Local testte aldığımız URL aşağıdaki gibi oluyor.

http://127.0.0.1:10000/devstoreaccount1/dosyalar/istanbulazurelogo.png?snapshot=2014-12-2T11:33:32.5370000Z

Güzel, bir snapshot yarattık ve belli ki ona ulaşabiliyoruz. Fakat ya bir blob'un birden çok snapshot'u alınmışsa? Onlara nasıl ulaşırız? İşte bu noktada klasik blob listeleme senaryosunda geri dönüyoruz. Normal blobları listelermiş gibi ilerlerken ek olarak SnapShot'ların da listelenmesi için bir parametre göndermemiz yeterli oluyor.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

CloudBlockBlob blob = container.GetBlockBlobReference("istanbul_azure_logo.png");

var snapshots = container.ListBlobs(blob.Name, true, BlobListingDetails.Snapshots);
foreach (CloudBlockBlob snap in snapshots)
{
    if(snap.IsSnapshot )
    {
        Response.Write(snap.SnapshotQualifiedUri);
    }                
}

Yukarıdaki kod içerisinde ListBlobs'a verdiğimiz BlobListingDetails işimizi görecektir. Ayrıca söz konusu metoda verdiğimiz ilk parametrede de eldeki blobun adını vererek sadece o bloba ait kayıtların dönmesini sağlamış oluyoruz. Ama burada ufak bir uyarıda da bulunmam gerek. ListBlobs'un ilk parametresi bir Prefix aslında. Yani one2one match olarak ismine göre Blob aramaz. O nedenle eğer prefix ile tutturabileceğinizden emin değilseniz içeride ayrıca BlobName kontrol yapmakta fayda var. Elimize gelen listede dolaşırken orijinal blobu dışarıdan bırakmak için IsSnapShot kontrolü yapıyor, sonrasında da SnapshotQualifiedUri'yi alıyoruz.

Doğal olarak bu noktadan sonra artık SnapShot'lar da birer blob oldukları için SnapShot silme işlemini de normal bir blob siler gibi yapabiliyoruz ama ufak bir sorun var :) O da normal şartlarda SnapShot'ı olan bir ana blobun SnapShot'ların hepsi silinmeden silinemiyor olması. Bunun için SnapShot listesi alıp for'la gezip tek tek SnapShot silmeye gerek yok ;)

[C#]

blob.Delete(DeleteSnapshotsOption.IncludeSnapshots);

Yukarıdaki gibi silme işlemine de DeleteSnapshotsOption verebilirsiniz. Burada IncludeSnapshots veya DeleteSnapshotsOnly diyebiliyorsunuz.

[C#]

birBlob.StartCopyFromBlob(snapshotBlob);

Eğer ki bir SnapShot Blob'unu alıp bir başka bloba kopyalamak isterseniz yukarıdaki kod yardımcı olabilir. Unutmayın ana blobları kopyalarken SnapShot'lar taşınmaz ve kopyalanmaz. Snapshot her zaman ilk yaratılığı bloba bağlıdır. StartCopyFromBlob metodunu bir snapshotı restore etmek için de kullanmanız gerekecek. SnapShot'tan orijinal blobun üzerine kopyalama yapmak yeterli olacaktır.

Birkaç detay...

SnapShotların çalışma şeklini bilmek ne için ne kadar para ödeyeceğinizi bilmek adına da önemli. Bir SnapShot aldığınızda eğer SnapShot yapıldıktan sonra Blob'da değişiklik olmadıysa ek bir ücret ödemiyorsunuz. Özetle aslında sadece değişen Block veya Page için SnapShot'larda para ödersiniz. Fakat ufak bir uyarı :) eğer SnapShot'lar sizin için çok önemliyse ve çok kullanacaksanız StorageClient ile beraber gelen UploadFile, UploadText, UploadStream veya UploadByteArray gibi metodları kullanmayın. Bu metodlar upload esnasından tamamen sıfırdan upload yaptıkları için tüm page'ler veya block'lar değişiyor ve bu da sıfırdan bir snapshot almış olmak anlamına geliyor. Nitekim bu şekilde de ilerlenebilir ama bilerek ilerlemekte fayda var :) Bahsettiğimiz metodların kullanımları kolay ama SnapShot'ta da ufak bir dezavantajları var. Eğer böyle kullanacaksanız tavsiyem SnapShot'larınızı doğru yönetin ki gereksiz yere binlerce SnapShot durmasın kenarlarda.

SnapShot'ları çok yoğun kullanacaksanız ideal yöntem bir alt seviyeye inip kaynaklarınızı doğrudan Page veya Block seviyesinde yönetmek. StorageClient içerisinde PutBlock ve PutBlockList gibi işinize yarayabilecek metodlar mevcut ;)

Kolay gelsin.

Blob konusunu inceledikçe aslında adam akıllı dosya tutabileceğimiz :) süper bir yer olduğu hissiyatı eminim ki sizin de içinizi kaplamıştır. Bu düşünce yapısı ile ilerlerken aslında akla gelebilecek daha bir sürü soru var. Örneğin bir sitede kullandığımız CSS, JavaScript dosyaları acaba sitenin bir parçası mı olmalı yoksa Blob'da mı durmalı :)

Nereden çıktı bu da şimdi diyebilirsiniz ama bir düşünün. En ufak CSS değişikliğinde tüm service package'ı tekrar yaratıp upgrade süreci başlatmak ister misiniz? Aynı soru belki sitenizde kullandığınız ve site tasarımının bir parçası olan görsel dosyaları için de geçerli. Sitedeki şirket logosu bir blobda dursa daha iyi olmaz mı?

Tüm bu soruları kendi kendimize sora duralım ister bu gibi ihtiyaçlar yüzünden olsun ister eski alışkanlıkar sonuç itibari ile Blog Storage'daki tek klasörlere yapısı olarak gözüken Container'lar bize yetmeyecek galiba :) Container'ları iç içe koyamayacağımızdan bahsetmiştim değil mi? Bu konuya canınız sıkılıyorsa işte süper olmasa da işin çözümü şöyle...

File System Etkisi

Bir sunucudaki klasörlerin web ortamına yansıma şekli malum URL'de kendini gösterir. Her ne kadar artıkl URLReWriting sayesinde delikanlılığın kitabı tekrar yazılsa da :) sonuç itibari URL'deki ayrımlar çoğu zaman bizim için mantıksal ayrımlar şeklinde dosyaların yönetimi için ciddi yardımcı bir öğedir. Aynı etkiyi Azure'da Blob Storage'da yakalayabilir miyiz? sorusuna "Evet" cevabını verebilirim :) ama birazdan her ne yaparsak yapalım sakın unutmayın :) aslında böyle birşey yok :) yapacağımız herşey sanal, hala bloblarınız bir container içerisinde olacak.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

BlobContainerPermissions containerPermissions = new BlobContainerPermissions();
containerPermissions.PublicAccess = BlobContainerPublicAccessType.Blob;
container.SetPermissions(containerPermissions);

var blob = container.GetBlockBlobReference("resimler/" + FileUploadControl.FileName);
blob.UploadFromStream(FileUploadControl.FileContent);

Response.Write(blob.Uri.ToString());

Çakallığın farkına vardınız mı? :) Blob ismimiz artık sadece dosya ismi değil. Yukarıdaki kod bloba isim olarak "resimler/" ile beraber dosya adını veriyor böylece blobun ismi "resimler/ornek.jpg" gibi birşey oluyor. Peki bunun URL'e yansıması nasıl olacak?

Blob adını istediğimiz gibi verince URL de istediğimiz gibi oldu
:) Blob adını istediğimiz gibi verince URL de istediğimiz gibi oldu :)

Nasıl? Güzel oldu mu :) Şimdi tabi ki diyeceksiniz ki... "tamam güzel de bu dosyaların yönetimini kolaylaştırmıyor" ve kesinlikle haklısınız :) Bu noktaya kadar sadece web sitemize girip bu adresi gören ziyaretçileri kandırmış olduk. Yoksa arka planda değişen hiçbirşey olmadı, olamaz da. Ammmaaa! :)

StorageClient diye bir wrapper kullanıyoruz ya :) İşte onun içindeki bazı ek ilginç hareketler Container'lar içerisinde sanal klasörler varmış gibi listelemeler yapabilmemizi sağlıyor. Böylece normal bir dosya sisteminden beklediğimiz gibi klasörleme mantığını kodda da kullanabiliyoruz.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();

CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
CloudBlobDirectory directory = container.GetDirectoryReference("resimler");
foreach (var blobItem in directory.ListBlobs())
{
    Response.Write(blobItem.Uri + "<br/>");
}

Yukarıdaki kodda Container'ımızı yakaladıktan sonra artık içinde klasörler varmış gibi "VirtualDirectory" yapısında ilerliyoruz. "resimler" adı Blob'ların adında yer alsa da bir klasörmüş gibi CloudBlobDirectory olarak alıp içindeki blobları listeleyebiliyoruz.

http://127.0.0.1:10000/devstoreaccount1/dosyalar/resimler/digerleri/
http://127.0.0.1:10000/devstoreaccount1/dosyalar/resimler/test.txt
http://127.0.0.1:10000/devstoreaccount1/dosyalar/resimler/v.jpg

Yukarıda örnek bir dosya listesi görebilirsiniz. Fark ettiyseniz yukarıda bir sorun var. Listenin ilk başkındaki nesne bir dosya değil. Deneme amaçlı olarak iç içe birden çok klasör yaratacak şekilde bloblara isim verirseniz bir CloudBlobDirectory'den alacağınız Blob listesi aslında içindeki klasörü de getirecektir. Bu durumdan kurtulmanın tek yöntemi gelen nesnelerin tipini kontrol etmek.

[C#]

CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
CloudBlobDirectory directory = container.GetDirectoryReference("resimler");
foreach (var blobItem in directory.ListBlobs())
{
    if (blobItem is CloudBlobDirectory)
    {
        foreach (var blobItemNested in ((CloudBlobDirectory)blobItem).ListBlobs())
        {
            Response.Write("Dosya:" + blobItemNested.Uri + "<br/>");
        }
    }
}

Kodun foreach ile başlayan noktasından itibaren manzaraya bakacak olursan aslında container adımız olan resimleri bir klasör olarak kabul edip içindeki blobları dönüyorum. Bu dönüş sürecinde :) gelen nesnenin bir CloudBlobDirectory olup olmadığını kontrol edip (yani klasör olup olmadığını bir anlamda anlayıp) eğer klasörse tekrar onun altından da bir ListBlobs çekerek dosyaları alabiliyoruz.

Normal bir dosya sistemi konseptine yaklaşmaya birkaç adım kaldı sanırım :) Bunlardan ilki klasör yaratbilme. Maalesef bu konuda bire bir System.IO'ya benzetilebilecek bir yapı yok. Unutmayın sonuçta sanal klasör dediğimiz şeyler aslında blobların isimleri :) Sanal klasör ve dosya ayrımını yapan StorageClient wrapper. Yani aslında şu an ne yapıyorsak bunlar sorgular şeklinde Container seviyesinde çalıştırılıyorlar.

İtiraf etmek gerekirse bu Directory yapısına Microsoft desteği ne zaman kesecek diye ben heyecanla bekliyorum. Eski SDK sürümlerinde bu gibi bir destek developerların Blob yapısına geçmeleri için güzel bir havuçtu ve klasör yapısına alışmış developerlara bir çıkış yolu sunuyordu. Fakat artık bu geçiş dönemininin az çok bittiğini de düşünürsek söz konusu geçiş araçlarına gerek var mı tartışılır. Sonuç itibari ile tüm bunlar, sanal klasörler vs doğal, organiz değil :) ve performansa etkisi olacaktır. Daha da kötüsü eğer bu yapıya çok odaklanır ve kendinizi kaptırırsanız blobların gerçek çalışma şeklini unutabilir ve bir anda kendinizi tüm blog yapısını efektif olmaktan çok uzak yöntemlerle kullanırken bulabilirsiniz. Özlete, bence sıkışmadıkça uzak durun :)

Kolay gelsin ;)

Dün Storage Account'ları incelerken Redundancy modelinden, Replication seçeneklerinden bahsetmiş fakat detayları incelememiştik. Bu yazıda tüm Storage servisleri için geçerli olan farklı redundancy modellerine göz atacağız.

Bir Storage Account yarattığınızda size sorulan sorulardan biri de "Replication" modeli oluyor. Azure ortamında Storage için şu an dört farklı Replication modeli söz konusu.

Replication modeli fiyatlandırma için de kritik. Replication modeli fiyatlandırma için de kritik.

Locally Redundant

Farklı Fault-Domain ve Upgrade-Domain'lerde olacak şekilde storage transactionlarınızın hepsi senkron bir şekilde toplam üç ayrı storage node'una gönderilir. Böylece Azure Storage servisinin arka planında bir donanım sorunu oluşsa da veriniz ulaşılabilir durumda olur.

Geo-Redundant

Locally Redundant modelde olduğu gibi aynı datacenter içerisinde yine üç kopya tutulur. Bu üç kopyanın haricinde asenkron olarak transactionlar bir başka bölgedeki DataCenter'a daha gönderilir. Diğer DC'ye gönderilen transaction da yine söz konusu DC'de üç kopya olarak tutulur. Bu seçenek genelde failover amacı ile kullanılır ve ilk bölgedeki üç kopya bir şekilde kaybedilirse veya altyapı aşağı inerse ikinci bölgedekinin kullanımı sağlanır. İki bölge arasındaki replication asenkron olduğu için failover esnasında kopyalanmamış bir miktar data tamamen kaybedilebilir.

Read-Access Geo-Redundant

Geo-Redundant yapı ile aynı özellikleri barındıran bu yapıda ek olarak okuma operasyonları birden çok bölgeye dağıtılarak daha yüksek okuma performansı elde edilir.

Zone Redundant

Bu modelde veriniz yine üç kopya olarak tutulur ve aynı Zone içerisindeki farklı DataCenter'larda saklanır. Zone Redundant modeli Geo-Redundant modelin çoğrafi olarak aynı bölge içerisine uygulanmış hali olarak düşünebilirsiniz. Tüm diğer modellerde şu anda Storage servislerinin hepsi desteklenirken, Zone Redundant modelde sadece Block Blob'lar destekleniyor. İleriki yazılarda bahsedeceğimiz Page Blob, Table ve Queue yapıları Zone Redundant olan Storage Account'larda kullanılamıyor.

Read-only Access Geo Redundant Storage nasıl kullanılır?

Eğer RA-GRS modelinin kendi kendine arka planda çalışacağını zaennediyorsanız aldanırsınız. Aslında tam olarak da aldanmış sayılmazsınız, eğer Storage Client Library 3.0 (ve sonraki) sürümünü kullanıyorsanız sorun olmayacaktır. Her halükarda gelin biz neler oluyor bir göz atalım.

Read Only İkincil Endpointler geldi... Read Only İkincil Endpointler geldi...

Yukarıdaki ekran görüntüsünde RA-GRS replikasyon modelini kullanan bir Storage Account ile kullanmayan bir Storage Account'un endpoint farklılıklarını görebilirsiniz. Görüldüğü üzere RA-GRS'yi seçtiğimizde Secondary Endpoint denilen ikinci erişim adresi oluşturuluyor. Böylece arka planda kopyalanan storage ortamına ulaşabiliyorsunuz. Tabi diğer yandan bu şekilde iki farklı endpoint verildiğinde de akla şu soru geliyor; "Hangisine ne zaman gideceğime ben mi karar vereceğim?" Özetle, amacımız failover olmadığına göre (öyle olsaydı zaten sadece Geo-Redundant kullanırdık) ve amacımız okuma operasyonlarını dağıtmak olduğuna göre bu işi biz mi yapacağız? Cevabı; Evet.

[C#]

CloudStorageAccount account = CloudStorageAccount.Parse("DefaultEndpointsProtocol=http;AccountName=daronragrs;AccountKey=DRxxxaPXg==;");
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

var blob = container.GetBlockBlobReference(FileUploadControl.FileName);
blob.UploadFromStream(FileUploadControl.FileContent);
Response.Write(blob.Uri.ToString());

Yukarıdaki kodu daha önceki yazılardan hatırlayabilirsiniz. Basit bir şekilde örnek bir container yaratıp içerisine de FileUpload kontrolü ile verilen bir dosyayı blog olarak atıyoruz. Bu kodun RA-GRS'a özel bir yanı yok. Kodu çalıştırdığımızda da doğal olarak bize blob endpoint URL olarak http://daronragrs.blob.core.windows.net/dosyalar/sample.txt geliyor. Storage hesabımızın replikasyon modeli RA-GRS olduğu için "sample.txt" dosyayı ikinci endpointin arkasına da kopyalanacak ve "http://daronragrs-secondary.blob.core.windows.net/dosyalar/sample.txt" adresinden de ulaşılabilir halde olacak. Böylece normal storage SLA'i olan %99.9, %99.99'a yükselmiş olacak :) Peki dışarıdan gelmeyen ve sizin kodunuz ile yaptığınız okuma operasyonlarını nasıl dağıtacaksınız? Özetle Storage Library size nasıl yardımcı olacak?

Storage Client içerisinde Location seçenekleri. Storage Client içerisinde Location seçenekleri.

Yukarıdaki ekran görüntüsünde de görebileceğiniz üzere her Client objesinin bir LocationMode özelliği var. LocationMode'a ister doğrudan Primary veya Secondary diyebileceğiniz gibi istersenizi önceliğe göre terih seçeneği de verebiliyorsunuz. Bu noktada özellikle dikkat etmeniz gereken nokta "SecondaryOnly" seçeneği. Eğer sadece "Secondary" terseniz container yaratma gibi kodlarınız "Read" operasyonu değil "Update / Write" operasyonu olduğu için çatlayacaktır. O nedenle eğer hem read hem write yapıyorsanız benim tavsiyem en kötü ihtimalde "SecondaryThenPrimary" tercihini kullanın.

Önceliklendirmeli ayarlarda (PrimaryThenSecondary veya SecondaryThenPrimary) bilmeniz gereken birkaç nokta daha var. Bu şekilde yapılan ayarlar aslında bir anlamda "Failover" ayarı oluyor. Eğer lokasyonlardan birine ulaşılamazsa RetryPolicy'sine göre SDK doğrudan diğer lokasyona yönlenecektir. Bu senaryoda tek istisna Secondary ile alakalı. Eğer Secondary'den 404 NotFound gelirse ayrı Retry Cycle içerisinde tekrar Secondary denenmeyecektir. Bunun nedeni ise birazdan bahsedeceğimiz replikasyon gecikmesi (replication delay) ile alakalı. Malum iki ayrı lokasyon arasında replikasyon yapılırken bir gecikme de söz konusu. Bu gecikme sizin servise yönlendirdiğiniz taleplerin yoğunluğuna göre bir noktada hissedilir hale gelebilir. Bunu da engellemenin yolu var :)

[C#]

ServiceStats stats = blobClient.GetServiceStats(
    new BlobRequestOptions() { 
        LocationMode = LocationMode.SecondaryOnly 
    });
if (stats.GeoReplication.Status == GeoReplicationStatus.Live)
{
    if (stats.GeoReplication.LastSyncTime.HasValue == true)
    {
        if (stats.GeoReplication.LastSyncTime.Value.AddSeconds(30) < DateTime.UtcNow)
        {
            blobClient.DefaultRequestOptions.LocationMode = LocationMode.PrimaryThenSecondary;
        }
        else
        {
            blobClient.DefaultRequestOptions.LocationMode = LocationMode.SecondaryThenPrimary;
        }
    }
}

İşte karşınızda LastSyncTime ve GeoReplicationStatus. Gelin adım adım yukarıdaki kodu inceleyelim. BlobClient üzerinden GetServiceStats dediğimizde RA-GRS olarak ayarlı hesabımızın replikasyon durumunun bilgisini istemiş oluyoruz. Replikasyon bilgisini ancak Secondary Endpoint'ten isteyebilirsiniz. O nedenle bu metoda parametre olarak BlobRequestOptions veriyoruz. Storage Client ile operasyon başına Secondary-Primary endpoint kararı almak isterseniz bu kararı bir BlobRequestOptions içine koyup metoda iletebilirsiniz. Böylece bizim örneğimizde BlobClient'ın genel karar mekanizmasından farklı olarak sadece GetServiceStats için Endpoint kararını ezerek doğrudan Secondary Endpoint'e gitmiş oluyoruz.

Bir sonraki adımda GeoReplication'ın Status'üne bakmamız gerek. Burada üç seçenek mevcut. Eğer Unavailable gelirse replikasyon falan yok ortada, her şey dağılmış ve Secondary'ye ulaşılamıyor demektir. Bu durumda kesinlikle Primary'ye gitmeniz gerek. Bizim kod örneğimizde işin bu kısmına dair bir yapı yok. Eğer Bootstrap gelirse daha replikasyon yeni başlatılmış ve data kopyalanıyor demektir. Bu durum sadece ilk olarak bir Storage Account'un GRS'den RA-GRS'ye alındığında gerçekleşir. Son olarak da Live değeri söz konusu. Bu da replikasyonun sağlıklı bir şekilde ayakta olduğunu gösteriyor.

Replikasyonun sağlığından emin olduktan sonra LastSyncTime'a bakıyoruz. Örneğimizde eğer LastSycnTime üzerinden 30 saniyeden çok zaman geçmiş ise Primary'ye geçmemişse Secondary'e gidiyoruz. Tabi her iki seçenekte de diğer endpointi failover olarak verebilmek adına "SecondaryOnly" veya "PrimaryOnly" yerine sürekli olarak "PrimaryThenSecondary" veya "SecondaryThenPrimary" kullandık. Tabi tüm bunlar sizin ihtiyaçlarınıza göre farklı tasarlanabilir. Hatta 30 saniye kuralı dahi tamamen değiştirilmesi gereken bir kural. Bu noktada sormanız gereken soru "Ne kadarlık bir replikasyon gecikmesini hoş görebilirim?" sorusu. Tüm bunları yanı sıra bir de şöyle bir sıkıntı var, ya son 1 dakikadır zaten replikasyonun güncellenmesini gerektirecek bir operasyon olmadıysa? Bu durumda zaten LastSyncTime'ın bir dakika önceyi göstermesi normal. Özetle şu anki zaman ile LastSyncTime arasında 30 saniyeden fazla süre olması replikasyonun tamamlanmış halde olup olmadığına dair bir fikir vermiyor. LastSyncTime her servis başına (Blob, Table, Queue) en son her iki lokasyonunda da kuyrukta replike edilecek şey kalmadan eşitlenmiş olduğu anın zamanını verir. Örneğin X adındaki blobunuz LastSyncTime'dan sonra her iki lokasyona da replike edilmiş ve eşitlenmiş olabilir ama Y adındaki blobunuz daha sonradan değiştirildiği için daha replikasyon kuyrunda kalmış olabilir. Bu nedenle tüm servis genelinde bir eşitlenme anı yaşanmadığından LastSyncTime X blobunun eşitlenme anında daha eski bir zamanı gösterebilir. Tüm bu senaryoların etrafından dolaşacak kodları yazmak da size kalıyor :) Geo-Replikasyon hiçbir zaman basit olmamıştır zaten.

Merak edebilecekleriniz :)

Aklınıza gelebilecek ve cevaplamadığımız bazı sorular var.

  1. Secondary Endpoint'in Credential/Key'leri ne durumda?
    Secondary ile Primary'nin key'ler tamamen aynı. Secondary'nin Key diye bir konsepti olmadığını düşünebilirsiniz.

  2. Shared Access Signature'lere ne oluyor?
    Hiç bir şey olmuyor. İmzalama esnasında kullandığınız account ismine -secondary eklememeniz çok önemli. İmza için kullandığınız key aynı olduğundan resourceURI'yi de DNS'den vs çıkarmamanız şart. Aksi halde problem çıkacaktır. Özetle aynı SAS ile secondary'ye Endpoint'e gidebilirsiniz.

  3. Storage Analytics kullanıyorum. Secondary için ne olacak?
    Secondary endpointin storage analytics'i primary ile aynı yerde saklanıyor. Metric tablolarının adları Secondary ve Primary olarak ayrıştırılacak saklanacaktır.

Hepinize kolay gelsin.

Uzun süredir Azure yazılarında sürekli olarak :) uygulamamızın çalıştığı sanal makinenin diskinin aslında kalıcı veri saklama alanı olarak kullanılamayacağından bahsediyorum. Nedenini sanırım bugüne kadar netleştirebilmişizdir :) Peki o zaman en basit haliyle kullanıcıdan aldığımız bir resim dosyasını nereye saklayacağız? Nereye saklamalıyız ki role'ümüzün tüm instance'ları bu dosyaya aynı şekilde ulaşabilsin?

Windows Azure Storage Servisleri

Paylaşımlı ve kalıcı bir dosya saklama alanı olarak Windows Azure Storage servisleri içerisinden Blob Storage'ı kullanmamız gerekiyor. Windows Azure Storage servislerinde toplam üç tane farklı servis var; blog, table ve queue. Biz bu yazımızda sadece Blob Storage konusunu inceleyeceğiz. İleriki yazılarda tabi ki diğer konulara da bakarız ;)

İlk Storage Account'umuzu
yaratırken İlk Storage Account'umuzu yaratırken

Yeni bir Storage Account yaratırken bir DataCenter veya Affinity Group seçmenin yanı sıra bir de "Replication" modelini seçmemiz gerekiyor. Replication modeli olarak default gelen model "Geo-Redundant" modeli. Şimdilik o seçenek ile ilerleyelim. Sonraki yazılarda Replication modellerinin detaylarına göz atacağız.

Not: Bir Storage Account yaratmak Microsoft'a para ödeyeceğiniz anlamına gelmez. Storage Accountlar içerisindeki tüm servislerin ücretlendirilme şekli kullanım üzerindendir. O nedenle yaratıp kenarda bıraktığınız bir storage account'un sizin için bir maliyeti olmaz.

Storage Account'un
endpointleri. Storage Account'un endpointleri.

Storage Account'unuz yaratıldıktan Dashboard'da yukarıdaki gibi bilgileri bulabilirsiniz. Her Storage Account'ta blob, table ve queue servisleri bulunur. Her servisin ise kendi endpointleri vardır. Endpointlerin listesi malum ekran görüntüsünde görülüyor. Zaten genel kural account adının sonuna servis adı sonrasına da core.windows.net eklenmesi şeklinde. Yine tekrar etmek istiyorum :) bunları yaratıp kullanmazsanız herhangi bir maliyeti yok :)

Storage account içerisindeki servislere ulaşırken ihtiyacınız olacak iki şey var, birincisi account adı veya endpoint adresi, ikincisi ise "Access Key" yani erişim anahtarı. Erişim anahtarı olarak iki tane erişim anahtarı veriliyor. Her iki anahtar da aynı işi görüyor. Peki neden ik tane var?

Access Key'leri yenilemek
için. Access Key'leri yenilemek için.

Diyelim ki keyleri yenilemek istediniz. Bunu hemen "Regenerate" diyerek yapabilirsiniz. Karşınıza gelecek yeni ekradan iki access key'den hangisini yenilemek istediğiniz sorulacaktır. Durum öyle ki :) access key'i yenile dediğiniz anda eskisi direk pasif oluyor. Peki canlı yayında olan uygulamanıza yeni access key'i verene kadar ne olacak? Uygulama aşağı mı inecek? Tabi ki olmaz. O nedenle key değiştirme senaryolarında eğer birinci key'i kullandıysanız onu önce bir ikinci key ile değiştiriyorsunuz :) ikinci key CSCFG ile tamamen tüm instancelara yayıldıktan sonra panele gelip birinci key'i regenrate ediyorsunuz. Birinci key ile beraber CSCFG yine tüm instancelara dağıldıktan sonra artık isterseniz ikinci key'i de regerate edebilirsiniz. İşte bu senaryo nedeniyle aynı işe yarayan iki access keyimiz mevcut.

Bloblar!

Blob kelimesinin açılımı "Binary Large Object". Bloblar Azure içerisinde paylaşımlı dosya saklama yerimiz.

Blob iç yapısı.
Blob iç yapısı.

Blob yapısı en dıştan bir "Account" ile başlıyor. Bunu zaten bir önceki adımda hesabımızı yaratırken gördük. Her account'un bir adı var ve en dış nesne olarak doğrudan account'un kendisi varsayabiliriz. Her account içerisinde birden çok Container olabiliyor. Container'ları şu noktada birer klasör gibi de düşünebilirsiniz ama bir istisna var :( container'ları içiçe koymak mümkün değil. Her container içerisinde de istediğiniz kadar Blob olabiliyor, yani dosya veya binary obje.

Her blobun bir de dışarıya açık HTTP Endpoint'i var. Yani bir container içerisine koyduğunuz bir bloba eğer uygun izinleri verdiyseniz bu blob / dosya doğrudan dışarıdan bir GET talebi ile alınabiliyor. GET talebinin gönderileceği yani dosyanın web adresi için şu şekilde oluşuyor;

http://{storage account adı}.blob.core.windows.net/{container adı}/{blob adı}

Her bir storage account şu an için en fazla 100TB veri saklayabiliyor. Bu sınıra storage account içerisindeki diğer servislerin (table, queue) sakladığı verilerin de dahil olacağını unutmamak gerek.

Page, Block Blob

Bloblar kendi içerlerinde ikiye ayrılıyorlar :) Page ve Block Blob'lar şeklinde... Genelde dosyalar eğer ki deli random read/write olmayacak block blob'larda tutulurlar. Bir block blob tek başına en fazla 200GB veri alabilir. Bir Page Blob ise tek başına 1TB veri saklayabilir.

Page blob ile block blob arasındaki farklılığın detaylarına biraz girmek gerekirse; Page Bloblar 512 byte'lık seriler şeklinde veriyi saklarken Block Blob'larda bloklar 4MB'a kadar çıkabilir. Block bloblar kendi içlerinde asenkron upload, birden çok kanal açımı, kaba bir transaction yapısı sunarken Page Blob'larda herşey anında commit olur. Genel olarak baktığımızda random read/write gerektiğinde, kabaca bir filesystem stream access gerektiğinde Page Blob'lar daha mantıklı olacaktır fakat chunk file / toplu dosya işlemlerinin bulunduğu senaryolarda block bloblar çok daha pratik olur. Örneğin son kullanıcıdan aldığınız bir resmi veya videoyu block blob'a kaydetmelisiniz :)

İlk blob kullanımımız!

Eh hadi bakalım :) İlk blob kullanımımıza doğru yola çıkalım. Temiz bir Azure projesini yine tertemiz bir web role ile aldıktan sonra WebRole'ün ayarlarına giderek Storage Account erişim bilgilerimizi tutacak yeni bir ayar bilgisini CSDEF ve CSCFG'ye koyalım.

Hesap ayarlarımızı Azure projesine
eklerken. Hesap ayarlarımızı Azure projesine eklerken.

Yukarıdaki ekran görüntüsünden de yakalayabileceğiniz üzere "Add Settings" diyerek istediğimiz isimde bir ayar tanımlayıp bunun da "Connection String" olacağını belirttikten sonra sağda belirlen mini düğmeye tıklayıp Storage Account bilgilerimizi girebileceğimiz ekrana geliyoruz. Burada istersenis Account Name ile Key'i girerek doğrudan Azure'daki Storage Account'tan blob servisini kullanın isterseniz "Microsoft Azure storage emulator" diyerek SDK ile beraber bilgisayarınıza yüklenen emülatörü kullanın. Programatik açıdan emülatör ile live servis arasında bir fark yok ;) o nedenle son testler haricinde local emülatörü kullanarak devam edebilirsiniz.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();

Ayarladığımız blobConnection connection stringini almak yerine ben şimdilik doğrudan DevelopmentStorageAccount kullanıyorum. Bunun üzerinden hemen kendimize bir CloudStorageAccount nesnesi yaratıyoruz. Unutmayın ki Storage Account ile ilgili tüm servisler normalde REST API'leri ile açılmış durumdalar. Bu REST API'leri C#'dan böyle rahat kullanabilmemizi sağlayan wrapper'lar Azure SDK sayesinde projemizde zaten bulunuyor. ASP.NET projemize referanslı gelen Microsoft.WindowsAzure.Storage.dll wrapper'ımızın ta kendisi. Eğer sizin projenizde referanslı değilse rahatlıkla Azure SDK'in kurulu olduğu klasörden bularak elle de referans alabilirsiniz.

Account nesnemizi yarattıken sonra bu nesne üzerinden de bir BlobClient yaratıyoruz. Böylece artık Storage Account üzerindeki blob servisi ile ilgili işlem yapacağımızı da belirlemiş olduk.

[C#]

CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

Biliyorsunuz bloblardaki herhangi bir blobu saklayabilmek için en azından bir container sahibi olmamız gerekiyor. O nedenle yukarıdaki kodda da hemen bir container yaratıyoruz. Elimizdeki blobclient üzerinden dosyalar adında bir container'ın referansını alıyoruz. Aslında böyle bir container şu anda yok :) ama zaten GetContainerReference dediğinizde bir REST API call gerçekleşmiyor. Tabi tüm bu kodları yazarken aslında sürekli olarak arka planda bir REST API Call'un yaratılmasını sağladığımızı ve o mantığa uygun kod yazdığımızı akılda bulundurmak gerek. Farklı bir benzetme ile :) belki de bunu yazdığımız anda SQL'e gönderilmeyen LINQ2SQL sorgularına da benzetebiliriz :) Çok farklı bir benzetme oldu farkındayım :)

Not: Container isimleri kesinlikle küçük harflerden oluşmalı. 3-63 karakter uzunluğunda olmalı.

Konumuza dönersek :) referansını aldığımız container'ın yaratılması için container nesnesi üzerinden CreateIfNotExists diyoruz ve işlem bitiyor. Artık "dosyalar" adında bir container nesnemiz var ve içine dosya atabiliriz :)

[C#]

var blob = container.GetBlockBlobReference(FileUploadControl.FileName);
blob.UploadFromStream(FileUploadControl.FileContent);

Herşey ne kadar basit ilerliyor değil mi? :) Elimizdeki container üzerinden bu sefer de bir BlockBlobReference alıyoruz. Blob reference alırken blob ismi olarak FileUpload kontrolünden gelen dosyanın ismini veriyoruz. Tabi yine container'da olduğu gibi şu anda böyle bir blob yok, sadece referansı var. Bir sonraki adımda o referans üzerinden UploadFromStream diyerek FileUpload'daki içeriği verdiğimiz anda Upload işlemi gerçekleşiyor ve blob fiziksel varlığına kavuşuyor :)

Eh hadi.. F5'e basın... ;)

Peki bu upload ettiğimiz resme nasıl ulaşacağız? Bunun için aşağıdaki gibi blob'dan URL istememiz gerekecek.

[C#]

var blob = container.GetBlockBlobReference(FileUploadControl.FileName);
blob.UploadFromStream(FileUploadControl.FileContent);
Response.Write(blob.Uri.ToString());

URL'i aldıktan sonra doğrudan tarayıcıya yazıp dosyayı görmek isterseniz dosyaya ulaşamadığınızı göreceksiniz. Ama bu noktadan önce linkin formatına dikkat edelim :) Daha önce konuştuğumuz storage account ve blob link yaratma şeklinde pek benzemiyor gibi. Normalde container adı ve sonra da blob adı gelmesi gerekirdi. Tabi onun için account adının da domain başında subdomain tadında durması gerekiyordu. Eğer bu projeyi Azure'daki canlı bir Storage Account'a yönlendirirseniz herşey aynen daha önce konuştuğumuz gibi olacaktır. Ama local emülatörde çalışıyorsanız maalesef bu linklerin yapısı daha farklı olacak. Lokal emülatörde sadece tek bir storage account olabiliyor ve table, queue, blob gibi servisler farklı IP'ler yerine farklı portlardan yayınlanıyor. Durum böyle olunca default account adı olan "devstoreaccount1" i de URL içerisinde görüyoruz. Özetle, bu emülatöre özel geçici bir durum :)

Dosyamıza ulaşamıyoruz. Dosyamıza ulaşamıyoruz.

Dosyaya ulaşamama problemimize geri dönersek. Problemin nedeni Container'ın izinleri ile alakalı. Varsayılan ayarlarla bir Container yaratıldığında sadece elinde key olan kişiler ulaşabiliyor. Yani biz zaten ConnectionString ile gittiğimiz için bizim için bir problem yok. Ama dışarıdan gelen bir kullanıcı URL ile doğrudan erişebilsin istiyorsak söz konusu Container'a ek izinler vermemiz şart.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

BlobContainerPermissions containerPermissions = new BlobContainerPermissions();
containerPermissions.PublicAccess = BlobContainerPublicAccessType.Blob;
container.SetPermissions(containerPermissions);

var blob = container.GetBlockBlobReference(FileUploadControl.FileName);
blob.UploadFromStream(FileUploadControl.FileContent);
Response.Write(blob.Uri.ToString());

Yukarıdaki koddaki tek farklılık container'a tüm bloblar için public access yani genele erişim hakkı vermemiz. Kodu tekrar çalıştırırsanız artık bu hak verilmiş ve container içerisinde her dosyaya kendi linki ile erişilebiliyor olacaktır. Toplamda Blob'larda üç çeşit ana erişim hakkı var.

  • Private; sadece key ile ulaşılabilir.
  • Blob; Blob izni verildiğinde blobun adresi biliniyorsa dosyaya ulaşılabilir.
  • Container; adresler bilinmeden de Container'lar içerisindeki blobların listesine ulaşılabilir.
[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");

foreach (var item in container.ListBlobs())
{
    ((ICloudBlob)item).Delete();
}

Hızlıca konumuza devam edecek olursak :) Örneğin bir Container içerisinde blobların listesine nasıl ulaşırım derseniz sanırım yukarıdaki kod yardımcı olabilir. Container üzerinden ListBlobs demeniz yeterli. Sonra tek tek gibip her blob'tan Uri'sini alabilir veya doğrudan "Delete" diyerek silebilirsiniz. Blob'lardaki dosyalarınızı geri almak isterseniz DownloadToStream size yardımcı olabilir.

[C#]

foreach (var blobItem in container.ListBlobs())
{
    using (var fileStream = System.IO.File.OpenWrite(Path.GetTempFileName()))
    {
        ((CloudBlob)blobItem).DownloadToStream(fileStream);
    } 
}

Sonuç olarak

Birincisi şunu söyliyim :) Blob konusu bitmedi :) ama sanırım akıllardaki çoğu soru işaretini cevaplamıştır. Azure ortamında 180 instance çalışan web role'ünüzün kalıcı dosya saklama yeri kesinlikle Blob'lar olacaktır. İşin güzel tarafı özellikle dışarıya açılacak dosyalarda blobların bunu doğrudan yapıyor olması. Böylece dosya downloadları, resimler gibi birçok şeyin trafiği aslında sunucunuz üzerinden geçmiyor bile. Blobların performansından, arka planda doğru şekilde dağılmasında Microsoft sorumlu çünkü orada zaten hem bandwidth :) hem REST API transaction başına para alıyorlar. Tabi storage alanı için de para alınıyor :)

Kolay gelsin.

SQL Azure ile birşeyler yapmaya başladınız fakat hala elinizdeki veritabanı ile nasıl ilerleyebileceğinizi kestiremiyorsunuz. Gelin o zaman beraber bir göz atalım. Birincisi ilk olarak web panelinde nasıl araçlarımız var bir ona bakalım.

SQL Azure veritabanını
yönetmek. SQL Azure veritabanını yönetmek.

SQL Azure üzerinde yarattığınız bir veritabanını yönetmek isterseniz en kolay yöntemlerden biri web üzerinden kullanılabilen management studio tadındaki Silverlight uygulaması. Uygulama açıldıktan sonra admin kullanıcı adı ve şifresini girerseniz herşey kendini yavaş yavaş göstermeye başlayacaktır.

Web tabanlı SQL Management
Studio Web tabanlı SQL Management Studio

Güzel bir Metro UI implementasyonu olan web tabanlı SQL Management Studio tamamen Silverlight ile yazılmış durumda. Uygulama içerisinde veritabanınız tasarım sürecinde ihtiyacınız olacak çoğu şeyi bulmakla beraber performans analizi (Query Analyzer) gibi şeyleri de kullanabilirsiniz. Eğer web tabanlı araçların hayranı değilseniz :) tabi ki hala masaüstünüzdeki SQL Server Management Studio da işinizi görebilir.

Var olan bir veritabanını Azure'a taşımak

Gelelim asıl konumuza. Elimizdeki bir veritabanı var ve onu SQL Azure ortamına taşımak istiyoruz. En basit yöntemlerden biri veritabanınızın scriptini alıp SQL Azure'a doğru çalıştırmak :) Bunu yaparken dikkat etmeniz gereken birkaç ufak nokta var, isterseniz onlara bir bakalım.

Bildiğimiz "Generate
Scripts" Bildiğimiz "Generate Scripts"

Bildiğimiz eski teknikler ile Generate Scripts diyerek yola çıkacağız. Ama bunu yapmadan önce SQL Server Management Studio'nun 2012 sürümüne sahip olduğunuzdan emin olun. Aksi halde göreceğimiz bazı seçenekler maalesef sizdeki kopyalarda bulunmayacaktır.

"Generate Scripts" sihirbazında yolunuza devam ederken, tam da Script'i nereye kaydetmek istediğiniz sorusu geldiğinde durun ve :) "Advanced" düğmesine tıklayın.

SQL Azure yolunda ufak
ayarlar. SQL Azure yolunda ufak ayarlar.

Yukarıdaki üç farklı ayar sanırım ekran görüntüsünde net olarak gözüküyordur :) Biri UDDT'lerden vaz geçmek anlamına geliyor. Diğeri ise "USE" keyword'ü ve filegroup gibi ayarlarla ilgili T-SQL'lerin yaratılmamasını sağlamak adına "Database Engine Type"ı değiştiriyor, son ayar ise sadece veritabanı şemasını değil berbarinde datayı da almamızı sağlıyor.

Tüm bu ayarlarla beraber aldığınız bir script SQL Azure tarafında %99.95 :) çalışacaktır.

DAC veya BACPAC Kullanarak veritabanınız taşımak

Veritabanınızı ve verisinizi Azure ortamına taşırken bir diğer seçenek de DAC veya BACPAC dosyalarını kullanmak. Bu dosyaları özellikle Azure'da Database Import ile beraber kullanabiliyor olmak büyük avantaj sağlayacaktır. Süreç özünde şöyle çalışıyor; ilk olarak uygun şekilde DAC veya BACPAC'i elinizdeki veritabanından extract/export ediyorsunuz. Sonra bu dosyayı Azure'da bir blob'a upload edip, Azure'daki Database Import'u kullanarak paketinizin Azure ortamındaki bir veritabanına import edilmesini sağlayabiliyorsunuz. Import süreci tamamen Azure Datacenter içerisinde olduğu için bir önceki senaryodaki gibi uzaktan bağlanıp T-SQL çalıştırmanın getireceği olası bağlantı problemleri ile uğraşmıyorsunuz. Malum, eğer veritabanınızın boyutu büyükse veya versiyonlama yapıyorsanız DAC veya BACPAC çok daha uygun ve pratik olacaktır.

DAC Export ederken... DAC Export ederken...

Export işlemini yaptıktan sonra hemen Azure portalına geçip yeni bir database yaratırken elinizdeki DACPAC dosyasını kullanabilirsiniz. Tabi bunun öncesinde dosyayı uygun bir Storage Account içerisindeki bir bloba atmayı unutmamanız gerek.

DACPAC Import ederken... DACPAC Import ederken...

DACPAC yerine BACPAC Export/Import yaparsanız sadece şema değil veritabanı içerisindeki veriniz de taşınacaktır. Bunun için en başta SQL Management Studio içerisinde "Extract Data-Tier Application" yerine "Export Data Tier Application" seçeneğini seçmeniz yeterli.

Deploy to Azure

Tüm bunlardan bahsederken SQL Management Studio içerisinde DACPAC/BACPAC komutlarının hemen yanında "Deploy database to Azure" seçeneği de dikkatinizi çekmiş olabilir :) Hatta kendi kendinize sormuş da olabilirsiniz "Bu adam neden bunu kullanmıyor?" diye... Aslında sözün özü, sadece o komutu kullanarak her şeyi halledebilirsiniz :) Sizin yerinizde otomatik olarak bir BACPAC yaratıp deployment'ı da yapacaktır.

Gördüğünüz üzere kullanılabilecek bir çok seçenek mevcut. Bunlardan hangisi size uygun ve pratik geliyorsa onunla devam edebilirsiniz. Arada ufak bir detay olarak klasik DACPAC/BACPAC Export seçeneklerinde aslında database'inizin Azure SQL Database uyumluluk testinden geçmediğini belirtmem gerek. O nedenle eğer ki Azure SQL Database'lerde desteklenmeyen bir şeyler varsa IMPORT esnasında güzel güzel hatalar alırsınız :) Benden söylemesi.

Hepinize kolay gelsin ;)

Twitter
RSS
Youtube
RSS Blog Search
Arşiv'de tüm yazıların listesi var. Yine de blog'da arama yapmak istersen tıkla!
Instagram Instagram