MS Certificering: AZ-204, overnieuw beginnen, deel 12

Ingediend door Dirk Hornstra op 21-dec-2021 15:55

Na het 1e blok: Azure App Service web apps (link), het 2e blok: Implement Azure Functions (link), het 3e blok: Develop solutions that use Blob Storage (link), het 4e blok: Develop solutions that use Azure Cosmos DB (link), het 5e blok: Implement infrastructure as a service solutions: (link), het 6e blok Implement user authentication and authorization (link), het 7e blok Implement secure cloud solutions (link), het 8e blok: Implement API Management (link), het 9e blok: Develop event-based solutions (link), het 10e blok:  Develop message-based solutions (link), het 11e blok: Instrument solutions to support monitoring and logging (link) is het nu tijd voor het 12e learning-block: Integrate caching and content delivery within solutions, link.

Module 1: Develop for Azure Cache for Redis, link.

Om te zorgen dat je web-applicatie snel blijft ga je zaken cachen. Hierdoor kun je data die veelvuldig opgeslagen wordt in een tijdelijke omgeving, dichter bij de applicatie. Als dat de afstand verkort tussen data opvragen en tonen (bijvoorbeeld omdat het anders uit een database moet komen) kun je de snelheid significant verbeteren.

Azure Cache voor Redis levert je een geheugen-gebaseerde memory-store: link.

Het verbetert de performance een heel stuk, vooral voor applicaties die gebruik maken van backend data stores. Low latency en hoge datadoorvoer.

Je kunt gebruik maken van de open-source vrsie (OSS Redis) en van een commerciele versie Redis Enterprise van Redis Labs. Hiermee kunnen applicaties geschaald worden, zowel binnen als buiten Azure.

Er zijn verschillende patterns die geïmplementeerd kunnen worden:

Data cache, databases zijn vaak te groot om die rechtstreeks in een cache te laden. Hiervoor wordt vaak het cache-aside pattern gebruikt (link). Als het systeem aanpassingen doet in de data wordt de cache ook bijgewerkt.

Content cache, veel webpagina's worden opgebouwd op basis van sjablonen die statische content gebruiken zoals headers, footers en banners. Deze zullen niet vaak wijzigen, met in-memory opslag kun je dat snel serveren.

Session store, wordt vaak gebruikt bij winkelwagens en andere historische data van gebruikers die een web applicatie koppelt aan cookies. Teveel data in een cookie opslaan kan voor vertraging zorgen, gebruik de cookie als een sleutel om data uit een database te halen. Met in-memory data van Redis is dat een stuk sneller dan uit een echte database ophalen.

Jobs en message queueing, applicaties voegen vaak taken in een queue toe omdat die lang duren. Langdurige taken worden vaak in volgorde door een andere server uitgevoerd, dit wordt task queueing genoemd.

Distributed transactions, applicaties vuren soms een aantal commando's af naar de back-end datastore die eigenlijk als 1 actie uitgevoerd moeten worden. Ze moeten of allemaal slagen of allemaal mislukken. Azure Cache voor Redis ondersteunt dit als een enkele transactie: link.

Abonnementen waarbij Redis beschikbaar is:

  • Basic, OSS Redis cache draaiend op een enkele VM. Geen SLA, ideaal voor dev/test.
  • Standard, OSS Redis cache draaiend op 2 VMs in een gerepliceerde configuratie.
  • Premium, high-performance Redis caches. Hoge doorvoer, lage latency, betere beschikbaarheid, meer features. Deployed naar VMs met meer power dan Basic en Standard.
  • Enterprise, high performance caches gesteund door Redis Enterprise. Dit abonnement ondersteunt Redis modules, zoals RediSearch, RedisBloo en RedisTimeSeries. Nog hogere beschikbaarheid dan Premium.
  • Enterprise Flash, cost-effectiee caches gesteund door Redis Enterprise. Uitbreiding van opslag naar non-volatile geheugen, goedkoper dan DRAM op een VM. Vermindert de kosten per GB aan geheugen.

Je kunt Redis configureren via de portal, Azure CLI en Azure PowerShell.

Om je cache goed te configureren, zul je goed moeten nadenken over de parameters die je bij aanmaken gaat gebruiken.

De naam is een globale unieke naam, omdat er een publieke URL aan gekoppeld wordt. Deze moet tussen 1 en 63 tekens lang zijn, nummers, letters en -
De naam kan niet beginnen met een - en meerdere - achter elkaar kan/mag ook niet.

Je moet kiezen waar de cache fysiek staat (welke regio). Zet altijd jouw applicatie en de cache in dezelfde regio. Maar er wordt ook gezegd: zet het zo dicht mogelijk bij de bezoeker van de site.

  • Basic: test/dev, 1 server, 53 GB geheugen, 20.000 connecties. Geen SLA
  • Standard: 99.9% SLA, 2 servers, overige specs gelijk.
  • Premium: 530 GB geheugen en 40.000 connecties.


Je kunt de hoeveelheid cache geheugen per tier instellen, door een cache leven van C0-C6 voor Basic/STandard en P0-P4 voor Premium.
Uitleg op de pagina met de prijzen: link.

De premium geeft je de zekerheid dat rampen geen invloed hebben, RDB persistence maakt regelmatig snapshots en bouwt de cache weer op, op basis van dat snapshot
AOF persistense slaat elke write-operatie op in een log en dat wordt minimaal elke seconde opgeslagen. Grotere bestanden dan RDB, maar minder verlies van data.
En er zijn nog andere instellingen, alleen beschikbaar voor Premium dus.

Een premium Redis cache kan gedeployed worden naar een virtueel netwerk in de cloud. Daardoor is de cache alleen beschikbaar voor die virtuele machines en applicaties binnen dat virtuele netwerk. Dit geeft een hogere mate van veiligheid.

Met premium Redis kun je clustering implementeren, zodat je dataset over meerdere nodes verdeeld wordt. Je moet hiervoor het aantal shards instellen, dat kunnen maximaal 10 zijn. De kosten hiervan zijn de kosten van de node maal het aantal shards.

Redis heeft een command-line tool om met de cache te werken, beschikbaar voor Windows: link.

Als je op andere platformen via de command-line interactie wilt, check dan de site: link.

Redis ondersteunt een aantal commando's. Syntax is COMMAND parameter1 parameter2 parameter3

Voorbeelden:

  • ping, ping de server, PONG als antwoord.
  • set [key] [value], stel een key/value in op de cache, OK bij succes.
  • get [key], vraag de waarde op uit de cache.
  • exists [key], controle of de sleutel bestaat, JA: 1, NEE: 0.
  • type [key], type van de waarde opvragen.
  • incr [key], verhoog de waarde die aan deze key hangt met 1, dat moet een integer of double zijn. Resultaat is de nieuwe waarde.
  • incrby [key] [amount], zelfde als incr, maar dan met eigen aantal wat je wilt optellen.
  • del [key], verwijder key/value uit de cache.
  • flushdb, verwijder alle keys en values uit de cache.


Je kunt een verlooptijd meegeven. Hierdoor wordt een key automatisch verwijderd, alsof je een del doet. TTL kan ingesteld worden op seconden of milliseconden. Minimale marge is 1 milliseconde. Als de TTL over 5 minuten ingesteld staat en je zet de Redis server uit en na 4 minuten weer aan, dan wordt na een minuut de key/value verwijderd (dus een exacte tijd).

Verbinden met de cache vanuit een client. Een client moet de hostnaam, poort en access key hebben. Deze kun je in de portal vinden via Settings, Access Keys. De hostnaam is de publieke URL. Je hebt 2 keys die je kunt gebruiken, behandel ze als wachtwoorden, dus sla ze secure op: Key Vault.

Vaak wordt een bestaande codebibliotheek gebruikt om verbinding te maken. Een populaire bibliotheek voor .NET is StackExchange.Redis: link.

Je kunt een soort connectie-string gebruiken:

[cache-name].redis.cache.windows.net:6380,password=[password-here],ssl=True,abortConnect=False

Met ssl geef je aan dat de connectie secure moet zijn, met abortConnection kun je instellen dat de connectie aangemaakt wordt ook als de server "nu even niet" beschikbaar is.

Andere parameters zijn hier na te lezen: link.

Voorbeeld van gebruik:


using StackExchange.Redis;
...
var connectionString = "[cache-name].redis.cache.windows.net:6380,password=[password-here],ssl=True,abortConnect=False";
var redisConnection = ConnectionMultiplexer.Connect(connectionString);

Als je de ConnectionMultiplexer hebt wil je 3 dingen doen: maak een connectie met een Redis database, maak gebruik van de publish/subscribe features van Redis (valt buiten de scope van 204) en krijg toegang tot een server voor onderhoud of monitoring.


// Connectie met de database:

IDatabase db = redisConnection.GetDatabase();

// waarde instellen

bool wasSet = db.StringSet("favorite:flavor", "i-love-rocky-road");

string value = db.StringGet("favorite:flavor");
Console.WriteLine(value); // displays: ""i-love-rocky-road""

// binaire waardes werken ook

byte[] key = ...;
byte[] value = ...;

db.StringSet(key, value);

De keys worden weergegeven als RedisKey types. Die converteert impliciet naar string of byte[]

De IDatabase heeft meer function om met de Redis cache te werken. Er zijn functies om te werken met hashes, lijsten, sets en sets met sortering.

In de source-code kun je de volledig lijst bekijken, hier volgt een kort overzicht: link.

  • CreateBatch, groepeer acties die naar de server gestuurd worden als 1 enkele actie (maar niet expliciet als 1 actie uitgevoerd hoeven worden).
  • CreateTransaction, hetzelfde als CreateBatch, maar nu moet wel alles of niets uitgevoerd worden.
  • KeyDelete, verwijderen key/value.
  • KeyExists, controle of de key in de cache bestaat.
  • KeyExpire, stel de TTL in op een key.
  • KeyRename, hernoem een key.
  • KeyTimeToLive, vraag de TTL van een key.
  • KeyType, string representatie van het type van de waarde die opgeslagen is, kan string, list, set, zset en hash zijn.


Andere commando's uitvoeren:


var result = db.Execute("ping");
Console.WriteLine(result.ToString()); // displays: "PONG"

Het resultaat is een RedisResult, een dataholder die 2 eigenschappen heft, Type, die in een string het type van het resultaat geeft: "STRING", "INTEGER" en IsNull, een true/false waarde om te controleren of je een NULL terug krijgt.

Met ToString() krijg je de actuele waarde terug.


// voorbeeld opvragen welke clients verbonden zijn aan list client

var result = await db.ExecuteAsync("client", "list");
Console.WriteLine($"Type = {result.Type}\r\nResult = {result}");

Door objecten te serialiseren naar JSON of XML kun je ze in de cache opslaan.


// voorbeeld opschonen van de connectie
redisConnection.Dispose();
redisConnection = null;

Hierna volgt een oefening waarbij je een app gaat koppelen aan een Azure Cache voor Redis via .NET Core.


// maak resource group

az group create --name az204-redis-rg --location <myLocation>

// maak een cache aan

redisName=az204redis$RANDOM
az redis create --location <myLocation> \
    --resource-group az204-redis-rg \
    --name $redisName \
    --sku Basic --vm-size c0

// ga in de portal naar de gegevens en pak de primary connection string

// maak een console app

dotnet new console -o Rediscache

// voeg het StackExchange package toe

dotnet add package StackExchange.Redis

// pas Program.cs aan

using StackExchange.Redis;
using System.Threading.Tasks;
// connection string to your Redis Cache    
static string connectionString = "REDIS_CONNECTION_STRING";

// pas main aan:

static async Task Main(string[] args)
{
    // The connection to the Azure Cache for Redis is managed by the ConnectionMultiplexer class.
    using (var cache = ConnectionMultiplexer.Connect(connectionString))
    {
        IDatabase db = cache.GetDatabase();

        // Snippet below executes a PING to test the server connection
        var result = await db.ExecuteAsync("ping");
        Console.WriteLine($"PING = {result.Type} : {result}");

        // Call StringSetAsync on the IDatabase object to set the key "test:key" to the value "100"
        bool setValue = await db.StringSetAsync("test:key", "100");
        Console.WriteLine($"SET: {setValue}");

        // StringGetAsync takes the key to retrieve and return the value
        string getValue = await db.StringGetAsync("test:key");
        Console.WriteLine($"GET: {getValue}");

    }
}

// dotnet build | dotnet run
// daarna zaken opruimen

az group delete -n az204-redis-rg --no-wait


Module 2: Develop for storage on CDNs, link.

Met een CDN heb je overal kopieën van je data/bestanden en zorg je dat dit dicht bij je bezoekers op te vragen is. Hiermee versnel je jouw site(s).

Azure CDN kan ook dynamische content die niet gecached kan worden versnellen, door verschillende POP (Point of Presence) in te zetten waardoor Border Gateway Protocol (BGP) overgeslagen kan worden.

betere performance en verbeterde user experience voor bezoekers, vooral in applicaties waar veel round-trips nodig zijn om content binnen te halen.
schaalbaarheid waardoor bij hoge load er niet 1 server volledig overbelast wordt, maar de load verdeeld wordt over meerdere punten.
en dit punt komt daar wel mee overeen, minder verkeer naar de originele server.

Een bestand blijft 7 dagen op de POP staan (tenzij in de TTL headers wat anders aangegeven staat).

Om Azure CDN te gebruiken heb je minimaal een CDN profiel nodig, dat is een collectie van CDN eindpunten. Elk eindpunt representeert een specifieke configuratie van aflevering en toegang tot content. Je kunt met meerder profielen zaken instellen, het organiseren op basis van internet domein, web applicatie. Omdat de prijs afhankelijk is van het CDN profile level moet je meerdere profielen maken als je met die prijzen wilt mixen: link.

Er zijn een aantal limieten namelijk, het aantal CDN profielen dat je aan kunt maken, het aantal eindpunten dat je in een profiel aan kunt maken en het aantal custom domains dat je aan een eindpunt kunt koppelen. Op deze site kun je er meer over lezen: link.

Azure CDN heeft 4 producten waar je uit kunt kiezen:

  • Azure CDN Standard from Microsoft
  • Azure CDN Standard from Akamai
  • Azure CDN Standard from Verizon
  • Azure CDN Premium from Verizon


Verschillen kun je hier bekijken: link.

Hoe zorg je dat wat in de cache staat een goede weergave is van het echte bestand/document/afbeelding?
Bij het Standaard abonnement worden caching rules ingesteld op het eindpunt level waarbij 3 configuratie-opties in te stellen zijn.
Andere abonnementen bieden meer opties, zoals:

  • Caching rules: deze kunnen globaal ingesteld worden (op alle content van een eindpunt) of op maat. De op maat regels zijn van toepassing op specifieke paden en bestandsextensies.
  • Query string caching: je kunt instellen hoe CDN moet acteren op een query string. Dit heeft geen effect op bestanden die niet gecacht kunnen worden.


De 3 opties zijn:

  • Negeer query strings, dit is de standaard mode.
  • Als er query strings binnen komen, skip de cache, vraag het origineel op.
  • Cache elke unieke URL. Voor elke losse query string krijg je dan ook een los cachebestand. Als bijna elke aanvraag uniek is, is dit niet een efficiënte methode.

In het eindpunt kun je in de portal Caching rules vinden en daar deze instellingen aanpassen.

Standaard TTL waardes:

  • algemene web aflever optimalisaties: 7 dagen
  • grote bestanden optimalisatie: 1 dag
  • streamen van media optimalisatie: 1 jaar


Om te zorgen dat altijd een juiste versie geladen wordt kun je in de URL een versie mee nemen. Als alternatief kun je de cache van de edge nodes "purgen", waardoor het ververst wordt. Een purge kun je uitvoeren op:

  • een eindpunt doen of alle eindpunten.
  • een bestand, door het pad van het bestand aan te geven.
  • baseren op wildcards (*) of via de root (/).

 


// opschonen data - als het heeeeeeeel veel is
az cdn endpoint purge \
    --content-paths '/css/*' '/js/app.js' \
    --name ContosoEndpoint \
    --profile-name DemoProfile \
    --resource-group ExampleGroup

// als je alvast wilt pre-loaden
az cdn endpoint load \
    --content-paths '/img/*' '/js/module.js' \
    --name ContosoEndpoint \
    --profile-name DemoProfile \
    --resource-group ExampleGroup
 

Met geo-filtering kun je content blokkeren of toestaan in bepaalde landen, gebaseerd op de landcode. In Azure CDN Standard kun je alleen de volledige site blokkeren of toestaan. Met Verizon en Akamai kun je bepaalde bestandsmappen instellen.

Als je in .NET met CDN wilt werken, dan kun je het Microsoft.Azure.Management.Cdn.Fluent package: link.


// client aanmaken

static void Main(string[] args)
{
    // Create CDN client
    CdnManagementClient cdn = new CdnManagementClient(new TokenCredentials(authResult.AccessToken))
        { SubscriptionId = subscriptionId };
}

// profielen en eindpunten tonen

private static void ListProfilesAndEndpoints(CdnManagementClient cdn)
{
    // List all the CDN profiles in this resource group
    var profileList = cdn.Profiles.ListByResourceGroup(resourceGroupName);
    foreach (Profile p in profileList)
    {
        Console.WriteLine("CDN profile {0}", p.Name);
        if (p.Name.Equals(profileName, StringComparison.OrdinalIgnoreCase))
        {
            // Hey, that's the name of the CDN profile we want to create!
            profileAlreadyExists = true;
        }
        //List all the CDN endpoints on this CDN profile
        Console.WriteLine("Endpoints:");
        var endpointList = cdn.Endpoints.ListByProfile(p.Name, resourceGroupName);
        foreach (Endpoint e in endpointList)
        {
            Console.WriteLine("-{0} ({1})", e.Name, e.HostName);
            if (e.Name.Equals(endpointName, StringComparison.OrdinalIgnoreCase))
            {
                // The unique endpoint name already exists.
                endpointAlreadyExists = true;
            }
        }
        Console.WriteLine();
    }
}

// profielen en eindpunten aanmaken -- profiel

private static void CreateCdnProfile(CdnManagementClient cdn)
{
    if (profileAlreadyExists)
    {
        //Check to see if the profile already exists
    }
    else
    {
        //Create the new profile
        ProfileCreateParameters profileParms =
            new ProfileCreateParameters() { Location = resourceLocation, Sku = new Sku(SkuName.StandardVerizon) };
        cdn.Profiles.Create(profileName, profileParms, resourceGroupName);
    }
}

// -- endpoint

private static void CreateCdnEndpoint(CdnManagementClient cdn)
{
    if (endpointAlreadyExists)
    {
        //Check to see if the endpoint already exists
    }
    else
    {
        //Create the new endpoint
        EndpointCreateParameters endpointParms =
            new EndpointCreateParameters()
            {
                Origins = new List<DeepCreatedOrigin>() { new DeepCreatedOrigin("Contoso", "www.contoso.com") },
                IsHttpAllowed = true,
                IsHttpsAllowed = true,
                Location = resourceLocation
            };
        cdn.Endpoints.Create(endpointName, endpointParms, profileName, resourceGroupName);
    }
}

// purgen van een eindpunt:

private static void PromptPurgeCdnEndpoint(CdnManagementClient cdn)
{
    if (PromptUser(String.Format("Purge CDN endpoint {0}?", endpointName)))
    {
        Console.WriteLine("Purging endpoint. Please wait...");
        cdn.Endpoints.PurgeContent(resourceGroupName, profileName, endpointName, new List<string>() { "/*" });
        Console.WriteLine("Done.");
        Console.WriteLine();
    }
}