BLOGas.lt
Sukurk savo BLOGą Kitas atsitiktinis BLOGas

Pingy - #8 PingReportItem ir PingReportRepository

Parašė Sergejus | 2010-12-22 23:06

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 sukūrėme Pinger servisą, kuris atlieka pagrindinę Pingy aplikacijos logiką – kreipiasi į nurodytus URL ir tikrina, ar serveris yra pasiekiamas. Šio kreipimosi rezultatas buvo talpinamas į PingReportRepository repozitoriją, kurią mes šioje dalyje ir įgyvendinsime.

PingReport esybės

Kaip ir PingItem atveju, pirma aprašysime modelį PingReportItem:

public class PingReportItem
{
    public string Client { get; set; }
    public string Url { get; set; }
    public DateTime CollectedOn { get; set; }
    public int StatusCode { get; set; }
    public TimeSpan ResponseTime { get; set; }
}

StatusCode – tai HTTP atsakymo kodas, o ResponseTime – laiko intervalas, per kiek mes gavome atsakymą iš serverio. Kadangi saugojimui naudosime Windows Azure Table Storage, pagal analogiją su PingItem būtina aprašyti esybę PingReportEntity:

public class PingReportEntity : TableServiceEntity
{
    public string Url { get; set; }
    public DateTime CollectedOn { get; set; }
    public int StatusCode { get; set; }
    public long ResponseTime { get; set; }
}

Atkreipkite dėmesį, kad šioje esybėje kliento pavadinimas bus naudojamas kaip PartitionKey, o RowKey bus sudarytas iš trejeto Client, Url ir CollectedOn. Paprastesniam darbui su PingReportItem ir PingReportEntity aprašykime konverterį:

public static class PingReportConverter
{
    public static PingReportItem ToPingReportItem(this PingReportEntity entity)
    {
        return new PingReportItem
        {
            Client = entity.PartitionKey,
            Url = entity.Url,
            CollectedOn = entity.CollectedOn,
            StatusCode = entity.StatusCode,
            ResponseTime = TimeSpan.FromMilliseconds(entity.ResponseTime)
        };
    }

    public static PingReportEntity ToPingReportEntity(this PingReportItem reportItem)
    {
        return new PingReportEntity
        {
            PartitionKey = reportItem.Client,
            RowKey = String.Format("{0}_{1}_{2}", reportItem.Client, reportItem.Url.SanitizeUrl(),
                                                  reportItem.CollectedOn.ToFileTimeUtc()),
            Url = reportItem.Url,
            CollectedOn = reportItem.CollectedOn,
            StatusCode = reportItem.StatusCode,
            ResponseTime = (long)reportItem.ResponseTime.TotalMilliseconds
        };
    }
}

PingReportRepository įgyvendinimas

PingReportRepository realizacija labai panaši į PingItemRepository:

public class PingReportRepository : IPingReportRepository
{
    private readonly CloudStorageAccount account;
    private readonly string pingReportEntityTable = typeof(PingReportEntity).Name;

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

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

    public IEnumerable<PingReportItem> GetClientReportItems(string client)
    {
        return CreateContext().CreateQuery<PingReportEntity>(pingReportEntityTable)
                              .Where(e => e.PartitionKey == client)
                              .AsTableServiceQuery()
                              .Execute()
                              .Select(e => e.ToPingReportItem());
    }

    public void AddReportItem(PingReportItem item)
    {
        var context = CreateContext();

        var pingReportEntity = item.ToPingReportEntity();
        context.AddObject(pingReportEntityTable, pingReportEntity);
        context.SaveChanges();
    }

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

        var entities = context.CreateQuery<PingReportEntity>(pingReportEntityTable)
                              .Where(e => e.PartitionKey == client)
                              .AsTableServiceQuery()
                              .Execute()
                              .ToList();

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

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

Kitoje dalyje…

Šioje dalyje mes aprašėme paskutinę pirmai iteracijai reikalingą repozitoriją PingReportRepoository ir su ja susijusias klases. Kitoje dalyje įgyvendinsime dar vieną Worker rolę – PingScheduler, atsakingą už kreipimosi į nurodytus URL užduočių sukūrimą bei valdymą.

Rodyk draugams

Pingy - #7 Pinger serviso kūrimas

Parašė Sergejus | 2010-12-04 16:28

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

5 dalyje mes esame aprašę PingTaskRepository, o praeitoje dalyje sužinojome kaip korektiškai daryti Windows Azure Queue žinučių apdorojimą. Šioje dalyje mes tai apjungsime į vieną visumą įgyvendinant Pinger servisą.

Pinger servisas

Pinger servisas – tai Class Library tipo projektas, kuris bus vykdomas Windows Azure Worker rolėje. Kiekviena Worker rolė privalo paveldėti iš RoleEntryPoint klasės, kurioje mes aprašysime du veiksmus: OnStart in Run. Pirmajame metode turi būti aprašomi visi veiksmai, kurie turi būti atlikti vieną kartą startuojant servisui, o antrajame – serviso verslo logika. Supaprastintai, Pinger serviso WorkerRole klasė atrodys taip:

public class WorkerRole : RoleEntryPoint
{
    private const int DequeueThreadhold = 5;
    private static readonly TimeSpan SleepInterval = TimeSpan.FromSeconds(60);

    private IPingTaskRepository pingTaskRepository;
    private IPingReportRepository pingReportRepository;

    public override bool OnStart() { }

    public override void Run() { }
}

Kaip tikriausiai pastebėjote, mes naudojame dvi repozitorijas: PingTaskRepository bei PingReportRepository. Pastarosios realizaciją pateiksiu kitoje dalyje. Konstantos DequeueThreadhold ir SleepInterval bus reikalingos korektiškam žinučių apdorojimui.

Metodas OnStart

Metode OnStart mes atliekame 4 pagrindinius veiksmus:

  • Nusakome kaip reaguoti į pasikeitimus Windows Azure rolės konfigūracijoje (pasikeitimai nebus iš karto pritaikomi rolei)
  • Nurodome iš kur skaityti konfigūracijos nustatymus (iš Windows Azure projekto ServiceDefinition failo)
  • Sukuriame Windows Azure Storage Account pagal nustatymus konfigūraciniame faile
  • Sukuriame prieš tai minėtas repozitorijas
public override bool OnStart()
{
    // Are any of the environment changes a configuration setting change? 
    // If so, cancel the event and restart the role
    RoleEnvironment.Changing += (sender, e) =>
        e.Cancel = e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange);

    // Provide the configSetter with the initial value
    CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
        configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)));

    var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
    pingTaskRepository = new PingTaskRepository(account);
    pingReportRepository = new PingReportRepository(account);

    return base.OnStart();
}

Metodas Run

Metodas Run įgyvendina praeitoje dalyje aprašyta žinučių apdorojimo šabloną:

  • Verslo logiką vykdomą pastoviai (while true)
  • Bandoma gauti PingTask objektą iš repozitorijos (eilės)
  • Jeigu pavyksta – vykdomas tolesnis darbas
  • Jeigu ne (eilėje nėra nei vienos užduoties) – laukiam SleepInterval laiką
  • Galiausiai, jeigu klaidų nebuvo arba buvo aptikta kenkėjišką žinutė – ją ištriname iš repozitorijos (eilės)

Trinant žinutę gali iškilti situacija, kad ji jau buvo ištrinta – tokias situacijas tiesiog ignoruojame

public override void Run()
{
    while (true)
    {
        PingTask task = null;
        var success = false;

        try
        {
            task = pingTaskRepository.GetTask();

            if (task != null)
            {
                var request = WebRequest.Create(task.PingItem.Url);
                var stopwatch = Stopwatch.StartNew();
                using (var response = (HttpWebResponse)request.GetResponse())
                {
                    stopwatch.Stop();
                    var item = new PingReportItem
                    {
                        Client = task.PingItem.Client,
                        Url = task.PingItem.Url,
                        CollectedOn = DateTime.UtcNow,
                        StatusCode = (int)response.StatusCode,
                        ResponseTime = TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds)
                    };

                    pingReportRepository.AddReportItem(item);
                }

                success = true;
            }
            else
            {
                Thread.Sleep(SleepInterval);
            }
        }
        catch (Exception)
        {
            // TODO: Log exception
            success = false;
        }
        finally
        {
            if (success || task != null && task.DequeueCount > DequeueThreadhold)
            {
                try
                {
                    pingTaskRepository.DeleteTask(task);
                }
                catch (StorageClientException ex)
                {
                    if (ex.ExtendedErrorInformation.ErrorCode == "MessageNotFound")
                    {
                        // Pop receipt must be invalid
                        // TODO: Log, so we can tune the visibility timeout
                    }
                    else
                    {
                        // TODO: Log
                    }
                }
            }
        }
    }
}

Kitoje dalyje…

Šioje dalyje parodžiau kaip sukurti Windows Azure Worker rolę, kitoje įgyvendinsime šioje dalyje panaudotą PingReportRepository.

Rodyk draugams

Pingy - #6 Windows Azure Queue korektiškas žinučių apdorojimas

Parašė Sergejus | 2010-11-09 12:00

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

Šioje dalyje, kaip ir žadėjau, aptarsime pagrindines problemas, kurios gali kilti apdorojant Windows Azure Queue pranešimus bei pateiksiu korektišką žinučių apdorojimo šabloną.

Žinučių apdorojimo scenarijai

Kaip jau rašiau, eilės leidžia logiškai atskirti servisus-gamintojus nuo apdorojimo servisų, taip suteikiant gerą plečiamumą (angl. scalability). Windows Azure Queue reikalauja, kad apdorojimo servisai:

  • galėtų korektiškai apdoroti tą patį pranešimą kelis kartus
  • nepriklausytų nuo apdorojamų žinučių eiliškumo

Išnagrinėkime kelis išskirtinius žinučių apdorojimo scenarijus bei jų apdorojimo būdus.

Scenarijus: žinutės apgodojimo metu servisas nulūžo.

Sprendimas: nieko nereikia daryti, Windows Azure Queue neišima žinutės iš eilės, o tiesiog pažymi kaip nematomą nurodytam laikui, po jo – žinutė vėl bus matoma kitiems apdorojimo servisams.

Scenarijus: tam tikra žinutė visada nulaužia apdorojimo servisą (kenkėjiška žinutė).

Sprendimas: bandyti apdoroti žinutes tik nurodytą skaičių kartų, po to – ignoruoti.

Scenarijus: žinutės apdorojimas viename iš servisų užtrūko ilgiau negu „nematomumo periodas“ (t.y. žinutė buvo jau grąžinta atgal į eilę).

Sprendimas: normaliai užbaigti žinutės apdorojimą, bandant šalinti žinutę iš eilės apdoroti situaciją, kaip žinutė jau yra ištrinta (kitas servisas apdorojo greičiau ir jau ją pašalino).

Žinučių apdorojimo šablonas

Žemiau pateikiamas žinučių apdorojimo šablonas, kuris atsižvelgia į visus aukščiau išvardintus scenarijus:

while (true)
{
    PingTask task = null;
    var success = false;

    try
    {
        // get message from the queue
        task = pingTaskRepository.GetTask();

        if (task != null)
        {
            // process task if any

            success = true;
        }
        else
        {
            // queue is empty - just sleep for some time
            Thread.Sleep(SleepInterval);
        }
    }
    catch (Exception)
    {
        // log
        success = false;
    }
    finally
    {
        // check for success or ensure it's not the poison message
        if (success || task != null && task.DequeueCount > DequeueThreadhold)
        {
            try
            {
                // delete message from the queue
                pingTaskRepository.DeleteTask(task);
            }
            catch (StorageClientException ex)
            {
                // the message has already been deleted
                if (ex.ExtendedErrorInformation.ErrorCode == "MessageNotFound")
                {
                    // log to tune the visibility timeout
                }
                else
                {
                    // log
                }
            }
        }
    }
}

Kitoje dalyje…

Šioje dalyje mes išnagrinėjome kaip korektiškai apdoroti Windows Azure Queue žinutes, kitoje dalyje pereisime prie Worker rolių įgyvendinimo. Pradėsime nuo Pinger, kuri ir panaudos aukščiau pateiktą šabloną.

Rodyk draugams