BLOGas.lt
Sukurk savo BLOGą Kitas atsitiktinis BLOGas

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

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

Pingy - #3 PingItem ir PingItemRepository

Parašė Sergejus | 2010-09-18 17:25

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

Šioje dalyje mes aprašysime esybę PingItem bei jos saugojimui skirtus Azure Table Storage objektus. Papildomai, įgyvendinsime pirmai iteracijai reikalingą PingItemRepository funkcionalumą.

PingItem ir susijusios klasės

Pati esybė atrodo labai paprastai:

public class PingItem
{
    public string Client { get; set; }
    public string Url { get; set; }
    public PingPeriod Period { get; set; }
}

Kreipimosi į serverį periodas nurodomas išvardijimo PingPeriod pagalba:

public enum PingPeriod
{
    QuarterHour = 15,
    HalfHour = 30,
    OneHour = 60,
    TwoHours = 120,
    SixHours = 360,
    TwelveHours = 720
}

Kaip matyti, reikšmės yra nurodomos minutėmis (tai bus svarbu vėliau). PingItem yra modelio POCO klasė, todėl mums reikalinga specialiai Windows Azure Table skirta klasė. Pavadinkime šią klasę PingEntity:

public class PingEntity : TableServiceEntity
{
    public string Url { get; set; }
    public int Period { get; set; }
}

Kodėl PingEntity neturi laukelio Client? Atsimenate PartitionKey? Mūsų atveju kaip PartitionKey ir bus naudojamas konkretus klientas. Tai leis visą su klientu susijusią informaciją saugoti kartu vienoje fizinėje saugykloje. Apibrėžkime pagalbine klasę PingItemConverter, kuri leis konvertuoti objektus iš ir į PingItem klasę:

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,
        };
    }
}

Atkreipkite dėmesį į metodą SanitizeUrl. Kadangi tiek PartitionKey, tiek RowKey turi ribojimus dėl leistinų simbolių, šis metodas užtikrina korektišką URL konvertavimą:

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;
    }
}

PingItem repozitorija

Pats laikas pereiti prie klasės PingItemRepository, kuri išsaugos mūsų PingItem objektą Windows Azure Table saugykloje. Mums reikalingas interfeisas atrodo taip:

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

Realizacija yra pateikiama žemiau, bet prieš tai norėtųsi pabrėžti, kad su Windows Azure Table duomenų kontekstą geriausiai kurti su kiekviena užklausa, o nesaugoti lokaliai.

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

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

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

    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();
    }

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

        var pingEntity = item.ToPingEntity();
        context.AttachTo(pingEntityTable, pingEntity, "*");
        context.DeleteObject(pingEntity);
        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();
    }

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

Jeigu atidžiai išnagrinėjote PingItemRepository kodą, tai tikriausiai pastebėjote metodą AsTableServiceQuery(). Jo dėka paprasta WCF Data Services užklausa yra konvertuojama į CloudTableQuery užklausą, kuri yra specialiai pritaikyta darbui su Windows Azure Tables (automatiškai nuskaito visus įrašus, o ne po 1000 kaip yra pagal nutylėjimą, įvykus ryšio klaidai, pakartoja operaciją ir t.t.). Taip pat jums gali kilti klausimas, kodėl metode Exists() aš panaudojau FirstOrDefault() vietoje, pavyzdžiui, Any(). Atsakymas paprastas – Any() nėra palaikomas su Windows Azure Tables.

Kitoje dalyje…

Taigi šioje dalyje mes apibrėžėme mūsų PingItem modelį bei susijusią Windows Azure Tables klasę ir įgyvendinome repozitoriją. Kitoje dalyje mes susidursime su pirmais NoSQL sunkumais: norint kitaip surūšiuoti lentelę, mums teks kurti visiškai naują lentelę su tais pačiais duomenimis.

Rodyk draugams

Pingy - #2 Windows Azure Table ir NoSQL mąstymas

Parašė Sergejus | 2010-09-07 21:33

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

Dėl informacijos gausos, nusprendžiau šiai temai skirti atskirą straipsnį. Prieš tęsiant, labai rekomenduoju perskaityti mano įvadą į Windows Azure Table.

Windows Azure Table trumpai

Trumpai priminsiu, Windows Azure Table yra paskirstyta (angl. distributed) NoSQL (neturi fiksuotos lentelių struktūros bei ryšių) tipo duomenų bazė. Tai reiškia, kad dalis jūsų duomenų bus saugoma vienoje saugykloje, o kita dalis – kitoje. Akivaizdu, kad duomenų gavimas iš skirtingų fizinių saugyklų reikalauja ne tik duomenų ištraukimo, bet ir perdavimo tinklu (kas nėra labai greita operacija). Dėl šios priežasties paskirstytose duomenų bazėse stengiamasi logiškai susijusią informaciją saugoti kartu, vienoje fizinėje saugykloje. Būtent šiam tikslui Windows Azure Table saugykloje ir egzistuoja PartitionKey sąvoka. Visos esybės, kurioms nurodytas tas pats PartitionKey, bus ne tik saugomos kartu, bet ir galės dalyvauti transakcijoje (paskirstytose duomenų bazėse transakcijos tarp skirtingų particijų nėra palaikomos). RowKey, kaip ir seka iš pavadinimo, yra įrašo raktas, kuris turi būti unikalus savo particijoje. Kitaip tariant, tikrąjį įrašo ID sudaro pora { PartitionKey; RowKey }.

Windows Azure Table klasių aprašymas

Norint saugoti duomenis Windows Azure Table saugykloje, reikia apibrėžti klasę, kuri paveldėtų iš TableStorageEntity arba turėtų savybes PartitionKey, RowKey ir Timestamp. PartitionKey ir RowKey paskirtį mes jau aptarėme, o savybės Timestamp paskirtis yra aiški – nusako paskutinio atnaujinimo laiką. TableStorageEntity klasės aprašas nurodytas žemiau:

public abstract class TableServiceEntity
{
    protected TableServiceEntity(string partitionKey, string rowKey);
    protected TableServiceEntity();
    public DateTime Timestamp { get; set; }
    public virtual string PartitionKey { get; set; }
    public virtual string RowKey { get; set; }
}

Mažiau žinoma, bet svarbu žinoti

Yra keletas svarbių aspektų, kurios būtina žinoti apie TableServiceEntity:

  • norint gauti konkretų įrašą, reikia nurodyti tiek PartitionKey, tiek RowKey; kitaip gausite visus nurodytos particijos duomenis arba visų particijų įrašus su nurodytu RowKey;
  • nepaisant fakto, kad Windows Azure Table leidžia nurodyti Where sąlyga bet kokiam esybės stulpeliui, niekada to nedarykite, nes indeksuoti yra tik PartitionKey ir RowKey stulpeliai;
  • įrašai yra automatiškai rūšiuojami pirma pagal PartitionKey, po to pagal RowKey ir to pakeisti negalima;
  • norėdami turėti duomenis išrūšiuotus pagal skirtingus stulpelius, teks pradubliuoti duomenis kiekvienam rūšiavimo variantui;
  • PartitionKey reikia parinkti tokį, kad prie jo galima būtų pririšti kuo daugiau susijusios informacijos; tuo pačiu metu PartitionKey neturi būti nei per daug konkretus, nei per daug platus.

Kitoje dalyje…

Tiek trumpai bendros informacijos apie Windows Azure Table saugyklą, kitoje dalyje pereisime prie PingItem esybės aprašymo bei susijusios repozitorijos kūrimo.

Rodyk draugams

Pingy - #1 Architektūra

Parašė Sergejus | 2010-09-05 22: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

Kūrimo iteracijos

Kadangi tai yra mano asmeninis laisvalaikio projektas, kuriam laiko nebus skiriama ypatingai daug, nusprendžiau Pingy kurti iteracijomis, tam kad kuo greičiau gauti bent dalį veikiančio funkcionalumo. Pirmoje iteracijoje bus įgyvendintos 3 bazinės funkcijos:

  • Kliento URL įvedimas ir redagavimas
  • Planuotojas (angl. scheduler), atsakingas už kreipimosi į serverį užduočių suformavimą ir įdėjimą į eilę
  • Kreipimosi į serverį servisai (angl. pingers), atsakingi už kreipimąsi į serverius ir gautos statistinės informacijos išsaugojimą

Kaip matote, šioje iteracijos mes nieko nedarysime susijusio su klientų autentifikacija bei statistinių duomenų atvaizdavimu. Šioje dalyje aš aprašysiu Pingy architektūrą, atitinkančią pirmos iteracijos tikslus.

Architektūra

Aiškumo dėlei, pirma pateiksiu sekų diagramą, iš kurios matysis informacijos srautai, kokie Windows Azure servisai bus reikalingi bei kaip bus užtikrinamas Pingy plečiamas (angl. scalability):

Pingy

Iš pateiktos diagramos matyti, kad Pingy sudarys tokios dalys:

  • Web Page – Windows Azure Web rolė, skirta kliento URL įvedimui bei redagavimui
  • Table Storage – ypatingai plečiama Windows Azure NoSQL tipo saugykla
  • Ping Scheduler – Windows Azure rolė, atsakingą už URL surinkimą pagal skirtingus laiko periodus (pvz., kas 15 min., kas 30 min., kas 60 min., kas 120 min. ir kas 720 min.)
  • Message Queue – Windows Azure eilių mechanizmas, užtikrinantis patikimą žinučių pateikimą
  • Pingers – Windows Azure rolė, atsakingą už kreipimąsi į serverius pagal nurodytus URL bei gautos statistikos išsaugojimą
  • Server – kliento nurodytas serveris (tiksliau jo URL), į kurį kreipimąsi kas nurodytą laiką

Šioje diagramoje nėra parodytos tokios trivialios operacijos kaip:

  • Visų kliento URL gavimas
  • Kliento URL redagavimas ir validavimas
  • Kitos pagalbinės operacijos

Pingy plečiamumas

Kaip bus užtikrinamas Pingy plečiamumas? Kliento URL ir kreipimosi į serverį dažnumas bus saugomas plečiamoje Windows Azure Table saugykloje. Ten pat bus saugoma ir kreipimosi statistika. Kreipimosi į serverį užduotis (angl. ping task) bus talpinama į Windows Azure eilę, kuri ne tik užtikrina žinutės gavimą ir apdorojimą, bet ir gali būti plečiama kuriant po atskirą eilę kiekvienam periodui. Pradiniame etape mes naudosime vieną bendrą eilę. Kreipimosi į serverį servisų (angl. Pingers) skaičius (tapatu virtualių mašinų skaičiui) nėra ribojimas – priklausomai nuo poreikio mes galime turėti nuo vieno iki kelių. Pradiniame variante startuosime su dviem. Ping Scheduler užduotis - nurodytam intervalui gauti visas nuorodas ir patalpinti atitinkamas užduotis į eilę. Pirminiame variante turėsime vieną servisą (t.y. vieną virtualią mašiną su šiuo servisu), kuriame kiekvienam periodui bus sukurta po atskirą giją. Esant plečiamumo poreikiui, nesunku bus patobulinti kodą, kad galima būtų paleisti kiekvienam periodui po nuosavą virtualią mašiną.

Taigi kaip tikriausiai pastebėjote, apie plečiamumą galvojau pirmoje vietoje. Jeigu turite minčių kaip dar labiau jį pagerinti – rašykite komentaruose!

Pingy esybės

Pirmoje iteracijoje mes dirbsime su tokiomis esybėmis:

  • Client – pagrindinė esybė, kuri figūruoja visoje sistemoje
  • PingItem – esybė, atitinkanti įrašą apie kliento URL ir kreipimosi į serverį dažnumą
  • PingTask – esybė, atitinkanti poreikį kreiptis į nurodytą kliento URL
  • PingReportItem – esybė, atitinkanti įrašą apie kreipimosi į serverį rezultatą

Kitoje dalyje…

Kitoje dalyje mes apibrėšime PingItem esybę, sukursime jai skirtą repozitoriją bei trumpai paaiškinsiu NoSQL mąstymo pagrindus, reikalingus efektyviam darbui su Table Storage. Kaip visada, laukiu jūsų pastabų / klausimų / pageidavimų komentaruose!

Rodyk draugams

Pingy – mokomės Windows Azure kuriant realią aplikaciją

Parašė Sergejus | 2010-09-02 21:23

Taip, ir vėl aš pradedu straipsnių ciklą, skirtą Windows Azure programavimui. Daugiau nei prieš pusmetį parašiau 7 dalių įvadą į Windows Azure:

Įvadas į Windows Azure – 1 dalis (Pagrindinės sąvokos)
Įvadas į Windows Azure – 2 dalis (Windows Azure Hello World ASP.NET puslapio kūrimas)
Įvadas į Windows Azure – 3 dalis (lokalios objektinės Windows Azure saugyklos konfigūravimas)
Įvadas į Windows Azure – 4 dalis (lokalios BLOB saugyklos panaudojimas)
Įvadas į Windows Azure – 5 dalis (lokalios BLOB saugyklos perkėlimas į Windows Azure)
Įvadas į Windows Azure – 6 dalis (esybių lentelių saugyklos panaudojimas)
Įvadas į Windows Azure – 7 dalis (XDrive panaudojimas)

Kas atsimena, po 7 dalies iš manęs staiga atėmė testinį Windows Azure prisijungimą. Kurį laiką bandžiau jį atgauti ir tik prieš kelis mėnesius per MVP kanalą Windows Azure grupė suteikė man naują prisijungimą. Vėl atsiradus galimybei, nusprendžiau pratęsti pažintį su Windows Azure, bet šį kartą kuriant realią aplikaciją.

Tie, kas užsiima Web programavimu .NET platformoje, tikriausiai yra susidūrę su viena IIS problema – po tam tikro neveikimo laiko jis tiesiog „užmiega“, dėl ko pirma HTTP užklausa užtrunka pakankamai ilgai. Yra keletas galimų būdų šiai problemai apeiti: AppPool Recycling; Windows servisas, kuris kas nustatytą laiką kreipiasi į nurodytą Web aplikaciją ir t.t. Aš nusprendžiau sukurti paslaugą (servisą), kuri leistų vartotojui nurodyti į kokį URL kokiu dažnumu reikia kreiptis. Tuo pačiu būtų kaupiama ir serverio veikimo statistika, pavyzdžiui, atsakos laikas, atsakymo HTTP kodas ir pan.

Tikrai žinau, kad panašios paslaugos egzistuoja (ir žymiai geresnės), bet mano tikslas yra kitas – geriau išmokti Windows Azure. Aplikaciją pavadinau Pingy (nuo angl. ping), kitame straipsnyje aš trumpai aprašysiu jos architektūrą, pateiksiu sekų diagramą bei paaiškinsiu kaip bus siekiama ypatingai geros greitaveikos bei plečiamumo (angl. scalability), kurį įgalina Windows Azure platformą.

Rodyk draugams