BLOGas.lt
Sukurk savo BLOGą Kitas atsitiktinis BLOGas

Skaidrės iš mano pristatymo Architecting Cloud-based Applications for Windows Azure Platform

Parašė Sergejus | 2010-10-30 15:41

Microsoft partnerių konferencijos metu dariau pristatymą iš programų, skirtų Windows Azure platformai, projektavimo.

Rodyk draugams

Microsoft PDC Live Keynote @ MS Lietuva Office

Parašė Sergejus | 2010-10-25 08:44

Jau ketvirtadienį prasideda konferencija Microsoft Professional Developer (PDC) 2010, kuri istoriškai pasižymi naujų produktų anonsų gausa. Šiais metais pirmą kartą kviečiame kartu stebėti transliaciją iš pagrindinio pristatymo Microsoft Lietuva ofise.

PDC, gera kompanija ir užkandžiai - kas gali būti šauniau ;)

Vieta: Microsoft Lietuva ofisas
Data: 2010-10-28, nuo 19:00
Registracija, vietų skaičius ribotas

Rodyk draugams

Pingy - #5 Windows Azure Queue ir PingTaskRepository

Parašė Sergejus | 2010-10-19 21:22

Primenu, Pingy – tai mano bandymas sukurti paslaugą, kuri kas tam tikrą laiką kreiptųsi į vartotojo nurodytą URL, taip neduodant IIS užmigti. Tuo pačiu metu bus renkama statistika apie serverio būseną, atsakos laikas ir pan. Pingy yra kuriamas specialiai Windows Azure mokymosi tikslais.

Turinys

Pingy - #1 Architektūra
Pingy - #2 Windows Azure Table ir NoSQL mąstymas
Pingy - #3 PingItem ir PingItemRepository
Pingy - #4 PingItem repozitorijos atnaujinimas metodu GetItemsByPeriod
Pingy - #5 Windows Azure Queue ir PingTaskRepository
Pingy - #6 Windows Azure Queue korektiškas žinučių apdorojimas
Pingy - #7 Pinger serviso kūrimas
Pingy - #8 PingReportItem ir PingReportRepository

Praeitoje dalyje mes užbaigėme PingItemRepository, o šitoje – aptarsime Windows Azure Queue bei jos panaudojimą aprašant PingTaskRepository.

Windows Azure Queue apžvalga

Eilės – labai galingas mechanizmas. Jų pagalba galima ne tik padalinti apkrovą tarp nurodytų serverių, bet ir logiškai atskirti servisus-gamintojus nuo apdorojimo servisų. Taip, N servisų-gamintojų gali siųsti pranešimus (uždavinius) į eilę, o M servisų imti tuos uždavinius paeiliui ir apdoroti. Windows Azure turi nuosavą eilių realizaciją – Windows Azure Queue. Iš plečiamumo pusės, mes galime keisti servisų-gamintojų ir apdorojimo servisų skaičių pagal poreikį nepriklausomai vienas nuo kito. Jeigu ateis momentas, kad lėčiausia vieta taps Windows Azure Queue – reikės naudoti kelias eiles skirtingoms užduotims atlikti (taip vadinamas eilių padalijimas, angl. Queue partitioning).

Windows Azure Queue pranešimų apdorojimui naudoja taip vadinamą dviejų fazių patvirtinimą:

  • Pasiima pranešimą (užduotį) iš eilės ir pažymį jį kaip nematomą
  • Jeigu pranešimo apdorojimo metu kils klaida, po tam tikro laiko pranešimas vėl taps matomu ir bus grąžintas į eilę
  • Jeigu pranešimas buvo sėkmingai apdorotas – jis turi būti išreikštinai pašalintas iš eilės

PingTask ir susijusios klasės

Prisiminkime pirmoje dalyje aprašytą architektūrą:

alt

Kaip matyti, kreipimasis į serverius yra inicijuojamas Ping Scheduler serviso, patalpinant atitinkamą pranešimą (užduotį) į eilę. Tada kiekvienas iš Pinger servisų iš eilės pasiima po užduotį ir pradeda jas vykdyti. Užduoties esybę mes modeliuosime naudojant klasę PingTask:

public class PingTask
{
    public string Id { get; set; }
    public string PopReceipt { get; set; }
    public int DequeueCount { get; set; }

    public PingItem PingItem { get; set; }
}

Čia PingItem – prieš tai aprašyta esybė, kuri gaunama ir Azure Table saugyklos. Kitos savybės yra nustatomos Windows Azure Queue pusėje ir reikalauja detalesnio aprašymo:

  • Id – unikalus pranešimo identifikatorius (automatiškai generuojamas)
  • PopReceipt – gavėjas, kuris išėmė pranešimą iš eilės
  • DequeueCount – nusako kiek kartų pranešimas buvo išimtas iš eilės (dažnai reiškia, kiek kartų jau buvo bandoma nesėkmingai apdoroti pranešimą, po ko nulūždavo apdorojimo servisas ir pranešimas buvo grąžinamas atgal į eilę)

Kadangi Windows Azure Queue turi nuosavą pranešimo klasę CloudQueueMessage, mums reikalingas labai paprastas esybių konverteris:

public static class PingTaskConverter
{
    public static PingTask ToPingTask(this CloudQueueMessage message)
    {
        return new PingTask
        {
            Id = message.Id,
            PopReceipt = message.PopReceipt,
            DequeueCount = message.DequeueCount,
            PingItem = message.AsString.FromJson<PingItem>()
        };
    }
}

CloudQueueMessage pranešimo turinys pasiekiamas dviem būdais – savybės AsByte arba AsString pagalba, todėl papildysime mūsų Helper klasę iš 3 dalies dviem metodais: ToJson ir FromJson:

internal static class Helper
{
    public static string SanitizeUrl(this string s)
    {
        var str = s.ToLower();
        str = Regex.Replace(str, @"(http|https)://", String.Empty);
        str = Regex.Replace(str, @"[^a-z0-9_.]", "-");

        return str;
    }

    public static string ToJson<T>(this T obj)
    {
        string json;

        var serializer = new DataContractJsonSerializer(typeof(T));
        using (var stream = new MemoryStream())
        {
            serializer.WriteObject(stream, obj);
            json = Encoding.Default.GetString(stream.ToArray());
        }

        return json;
    }

    public static T FromJson<T>(this string json)
    {
        T obj;

        var serializer = new DataContractJsonSerializer(typeof(T));
        using (var stream = new MemoryStream(Encoding.Default.GetBytes(json)))
        {
            obj = (T)serializer.ReadObject(stream);
        }

        return obj;
    }
}

PingTask repozitorija

Darbui su eile, aprašysime labai paprastą repozitorijos interfeisą IPingTaskRepository:

public interface IPingTaskRepository
{
    PingTask GetTask();
    void AddItem(PingItem item);
    void DeleteTask(PingTask task);
    void ClearTasks();
}

Šio interfeiso realizacija pateikiama žemiau:

public class PingTaskRepository : IPingTaskRepository
{
    private readonly CloudQueue queue;
    private readonly string queueName = typeof(PingTask).Name.ToLowerInvariant();
    private readonly TimeSpan visibilityTimeout = TimeSpan.FromMinutes(3);

    public PingTaskRepository(CloudStorageAccount account)
    {
        var client = account.CreateCloudQueueClient();
        queue = client.GetQueueReference(queueName);
        queue.CreateIfNotExist();
    }

    public void AddItem(PingItem item)
    {
        var message = new CloudQueueMessage(item.ToJson());
        queue.AddMessage(message);
    }

    public PingTask GetTask()
    {
        PingTask task = null;

        var message = queue.GetMessage(visibilityTimeout);
        if (message != null)
        {
            task = message.ToPingTask();
        }

        return task;
    }

    public void DeleteTask(PingTask task)
    {
        queue.DeleteMessage(task.Id, task.PopReceipt);
    }

    public void ClearTasks()
    {
        queue.Clear();
    }
}

Kodas yra pakankamai paprastas, bet verta paminėti du dalykus:

  • nematomumo periodas nustatomas trims minutėms po kurio jeigu pranešimas nėra ištrintas, jis bus grąžintas atgal į eilę
  • eilės pavadinimas privalo būti iš mažųjų raidžių, todėl išreikštinai darome ToLowerInvariant

Kitoje dalyje…

Pamatę PingTaskRepository realizaciją, gali kilti klausimas dėl jos panaudojimo (tiksliau kaip teisingai apdoroti skirtingas išskirtines situacijas). Kitoje dalyje pabandysiu aprašyti visas įmanomas problemas, kurios gali kilti apdorojant Windows Azure Queue pranešimus bei pateiksiu teisingo apdorojimo šabloną.

Rodyk draugams

Klaidų svarbos lygiai

Parašė Sergejus | 2010-10-11 09:18

Darbe pabandėme apibendrinti kada koks svarbos lygis turi būti suteikiamas klaidai. Gavosi tokia lentelė:

Event Severity Level Enabled in Production Reaction from IT Goes to Bug Tracker Event Description
Fatal Yes Immediate reaction Yes An unrecoverable error just occurred, business critical service is down or SLA cannot be guaranteed.Here goes errors like cannot connect to database or service, network is unavailable, application has been killed and etc. Usually those problems should be infrastructure problems which must be fixed by IT.
Error Yes Follow up ASAP Yes Non business critical error just occurred; this is an attempt to identify where it happened and what caused it for later investigation.Usually those problems will be fixed by developers.
Warning Yes No reaction No Semantic error or possible SLA problems, this is for the Team internal usage only.
Info No No reaction No Something interesting happened and it needs to be documented, this is for the Team internal usage only.
Debug No No reaction No This is the data needed when one get the call at 2 a.m. and has to diagnose the problem immediately.

O kokius klaidų lygius jus naudojate? Kaip patys atskiriate kada tai yra Error, o kada Warning ir pan. Labai įdomu sužinoti skaitytojų patirtį.

Rodyk draugams

Aš jau nebe C# MVP…

Parašė Sergejus | 2010-10-08 18:30

… o Windows Azure MVP. Nuo spalio 1 dienos, atsiradus naujai MVP sričiai – Windows Azure, mano kompetencijos sritis irgi buvo pakeista. Man yra didelė garbė būti tarp pirmų šios srities nominantų.

Rodyk draugams

TDD pristatymo medžiaga iš Lietuvos .NET vartotojų grupės susitikimo

Parašė Sergejus | 2010-10-05 15:39

Tapo prieinama Martyno Mikalajuno pristatymo medžiaga Test Drivent Development (TDD) tematika iš paskutinio Lietuvos .NET vartotojų grupės susitikimo.

Rodyk draugams

Pingy - #4 PingItem repozitorijos atnaujinimas metodu GetItemsByPeriod

Parašė Sergejus | 2010-10-04 19:23

Primenu, Pingy – tai mano bandymas sukurti paslaugą, kuri kas tam tikrą laiką kreiptųsi į vartotojo nurodytą URL, taip neduodant IIS užmigti. Tuo pačiu metu bus renkama statistika apie serverio būseną, atsakos laikas ir pan. Pingy yra kuriamas specialiaiWindows Azure mokymosi tikslais.

Turinys

Pingy - #1 Architektūra
Pingy - #2 Windows Azure Table ir NoSQL mąstymas
Pingy - #3 PingItem ir PingItemRepository
Pingy - #4 PingItem repozitorijos atnaujinimas metodu GetItemsByPeriod
Pingy - #5 Windows Azure Queue ir PingTaskRepository
Pingy - #6 Windows Azure Queue korektiškas žinučių apdorojimas
Pingy - #7 Pinger serviso kūrimas
Pingy - #8 PingReportItem ir PingReportRepository

Praeitoje dalyje mes apibrėžėme pagrindinę PingItem esybę bei su ja susijusią repozitoriją. Šiandien aš parodysiu kaip viena, reliacinėse duomenų bazėse triviali, užklausa gali NoSQL sprendime priversti dubliuoti duomenis.

Metodas GetItemsByPeriod

Priminsiu, praeitame straipsnyje mes turėjome tokį PingItemRepository interfeisą:

public interface IPingItemRepository
{
    IEnumerable<PingItem> GetClientItems(string client);
    bool Exists(PingItem item);
    void AddItem(PingItem item);
    void RemoveItem(PingItem item);
    void RemoveClientItems(string client);
}

Prisiminkime, kad mums reikia ne tik saugoti informaciją, bet ir kreiptis į nurodytus URL. Pas mus tas mechanizmas bus realizuotas pakankamai paprastai: kiekvienai PingPeriod išvardijimo reikšmei bus sukurta po giją, kuri kas nurodytą laiką užklaus į kokius URL reikia kreiptis ir patalpins atitinkamas užduotis į eilę. Už tai atsakys servisas Ping Scheduler. Specialiai jam mums reikia atnaujinti IPingItemRepository metodu GetItemsByPeriod:

public interface IPingItemRepository
{
    IEnumerable<PingItem> GetItemsByPeriod(PingPeriod period);
    IEnumerable<PingItem> GetClientItems(string client);
    bool Exists(PingItem item);
    void AddItem(PingItem item);
    void RemoveItem(PingItem item);
    void RemoveClientItems(string client);
}

NoSQL ypatumai

Iš pirmo žvilgsnio, metodo realizacija gali pasirodyti triviali ir atrodyti taip:

public IEnumerable<PingItem> GetItemsByPeriod(PingPeriod period)
{
    return CreateContext().CreateQuery<PingEntity>(pingEntityTable)
                          .Where(e => e.Period == (int)period)
                          .AsTableServiceQuery()
                          .Execute()
                          .Select(e => e.ToPingItem());
}

Pagrindinė šios realizacijos problema – ypatingai lėta greitaveika ir ribotas plečiamumas. Prisiminkime, duomenys Windows Azure Table saugykloje gali būti saugomi skirtinguose fiziniuose įrenginiuose. Tai reiškia, kad išreikštinai nenurodant PartitionKey bus ieškoma visose saugyklose! Be abejo tai yra labai neoptimalu, todėl reikia optimizuoti užklausą – saugoti PingItem esybes sugrupuotas pagal esamus periodus. Vienintelis būdas pasiekti tai su Windows Azure Table –  dubliuoti duomenis apibrėžiant naują lentelę ir jai skirtą modelį PeriodicPingEntity:

public class PeriodicPingEntity : TableServiceEntity
{
    public string Client { get; set; }
    public string Url { get; set; }
}

Šiame modelyje PingPeriod yra naudojamas kaip PartitionKey. Papildykime mūsų pagalbinę esybių konvertavimui skirtą klasę PingItemConverter:

public static class PingItemConverter
{
    public static PingItem ToPingItem(this PingEntity item)
    {
        return new PingItem
        {
            Client = item.PartitionKey,
            Url = item.Url,
            Period = (PingPeriod)item.Period,
        };
    }

    public static PingEntity ToPingEntity(this PingItem item)
    {
        return new PingEntity
        {
            PartitionKey = item.Client,
            RowKey = String.Format("{0}_{1}", item.Client, item.Url.SanitizeUrl()),
            Url = item.Url,
            Period = (int)item.Period,
        };
    }

    public static PingItem ToPingItem(this PeriodicPingEntity item)
    {
        return new PingItem
        {
            Client = item.Client,
            Url = item.Url,
            Period = (PingPeriod)Int32.Parse(item.PartitionKey),
        };
    }

    public static PeriodicPingEntity ToPeriodicPingEntity(this PingItem item)
    {
        return new PeriodicPingEntity
        {
            PartitionKey = ((int)item.Period).ToString(),
            RowKey = String.Format("{0}_{1}", item.Client, item.Url.SanitizeUrl()),
            Client = item.Client,
            Url = item.Url,
        };
    }
}

PingItemRepository atnaujinimas

Kadangi PeriodicPingEntity modeliai iš esmės tiesiogiai susiję su PingEntity, mums reikės ne tik įgyvendinti GetItemsByPeriod metodą, bet ir papildyti kitus metodus operacijomis su PeriodicPingEntity. Pilna (ir galutinė) PingItemRepository realizacija atrodo taip:

public class PingItemRepository : IPingItemRepository
{
    private readonly CloudStorageAccount account;
    private readonly string pingEntityTable = typeof(PingEntity).Name;
    private readonly string periodicPingEntityTable = typeof(PeriodicPingEntity).Name;

    public PingItemRepository(CloudStorageAccount account)
    {
        this.account = account;

        var client = account.CreateCloudTableClient();
        client.CreateTableIfNotExist(pingEntityTable);
        client.CreateTableIfNotExist(periodicPingEntityTable);
    }

    public IEnumerable<PingItem> GetItemsByPeriod(PingPeriod period)
    {
        return CreateContext().CreateQuery<PeriodicPingEntity>(periodicPingEntityTable)
                              .Where(e => e.PartitionKey == ((int)period).ToString())
                              .AsTableServiceQuery()
                              .Execute()
                              .Select(e => e.ToPingItem());
    }

    public IEnumerable<PingItem> GetClientItems(string client)
    {
        return CreateContext().CreateQuery<PingEntity>(pingEntityTable)
                              .Where(e => e.PartitionKey == client)
                              .AsTableServiceQuery()
                              .Execute()
                              .Select(e => e.ToPingItem());
    }

    public bool Exists(PingItem item)
    {
        var entity = item.ToPingEntity();
        return CreateContext().CreateQuery<PingEntity>(pingEntityTable)
                              .Where(e => e.PartitionKey == entity.PartitionKey)
                              .Where(e => e.RowKey == entity.RowKey)
                              .FirstOrDefault() != null;
    }

    public void AddItem(PingItem item)
    {
        var context = CreateContext();

        var pingEntity = item.ToPingEntity();
        context.AddObject(pingEntityTable, pingEntity);
        context.SaveChanges();

        var periodicPingEntity = item.ToPeriodicPingEntity();
        context.AddObject(periodicPingEntityTable, periodicPingEntity);
        context.SaveChanges();
    }

    public void RemoveItem(PingItem item)
    {
        var context = CreateContext();

        var pingEntity = item.ToPingEntity();
        context.AttachTo(pingEntityTable, pingEntity, "*");
        context.DeleteObject(pingEntity);
        context.SaveChanges();

        var periodicPingEntity = item.ToPeriodicPingEntity();
        context.AttachTo(periodicPingEntityTable, periodicPingEntity, "*");
        context.DeleteObject(periodicPingEntity);
        context.SaveChanges();
    }

    public void RemoveClientItems(string client)
    {
        var context = CreateContext();

        var pingEntities = context.CreateQuery<PingEntity>(pingEntityTable)
                                  .Where(e => e.PartitionKey == client)
                                  .AsTableServiceQuery()
                                  .Execute()
                                  .ToList();

        foreach (var entity in pingEntities)
        {
            context.DeleteObject(entity);
        }
        context.SaveChanges();

        foreach (var pingEntity in pingEntities)
        {
            var periodicPingEntity = pingEntity.ToPingItem().ToPeriodicPingEntity();
            context.AttachTo(periodicPingEntityTable, periodicPingEntity, "*");
            context.DeleteObject(periodicPingEntity);
        }
        context.SaveChanges();
    }

    protected TableServiceContext CreateContext()
    {
        return new TableServiceContext(account.TableEndpoint.ToString(), account.Credentials);
    }
}

Kitoje dalyje…

Kaip matote, programuojant debesims ir mąstant apie plečiamumą, kartais iš pirmo žvilgsnio paprastas uždavinys gali pavirsti visai netrivialiu. Kitoje dalyje mes pereisime prie kreipimosi į URL užduočių formavimo, o tiksliau prie PingTaskRepository klasės ir Windows Azure Queue panaudojimo.

Rodyk draugams