Snabbstart: Använda Azure Cache for Redis i .NET Core

I den här snabbstarten införlivar du Azure Cache for Redis i en .NET Core-app för att få åtkomst till en säker, dedikerad cache som kan nås från alla program i Azure. Du använder specifikt StackExchange.Redis-klienten med C#-kod i en .NET Core-konsolapp.

Hoppa till koden på GitHub

Om du vill hoppa direkt till koden kan du gå till snabbstarten för .NET Core GitHub.

Förutsättningar

Skapa en cache

  1. Om du vill skapa ett cacheminne loggar du in Azure Portal och väljer Skapa en resurs.

    Skapa en resurs markeras i det vänstra navigeringsfönstret.

  2. På sidan Nytt väljer du Databaser och väljer sedan Azure Cache for Redis.

    På Ny är Databaser markerat och Azure Cache for Redis är markerat.

  3. På sidan Ny Redis Cache konfigurerar du inställningarna för den nya cachen.

    Inställning Välj ett värde Beskrivning
    Prenumeration I listrutan väljer du din prenumeration. Prenumerationen där den nya instansen ska skapas Azure Cache for Redis instansen.
    Resursgrupp Välj en resursgrupp i listrutan eller välj Skapa ny och ange ett nytt resursgruppnamn. Namnet på den resursgrupp där du vill skapa ditt cacheminne och andra resurser. Genom att placera alla dina appresurser i en resursgrupp kan du enkelt hantera eller ta bort dem tillsammans.
    DNS-namn Ange ett unikt namn. Cachenamnet måste vara en sträng mellan 1 och 63 tecken som endast innehåller siffror, bokstäver eller bindestreck. Namnet måste börja och sluta med en siffra eller bokstav och får inte innehålla efterföljande bindestreck. Cacheinstansens värdnamn blir <DNS name> .redis.cache.windows.net.
    Plats Välj en plats i listrutan. Välj en region nära andra tjänster som ska använda ditt cacheminne.
    Cachetyp I listrutan väljer du en nivå. Nivån avgör storlek, prestanda och funktioner som är tillgängliga för cachen. Mer information finns i Översikt över Azure Cache for Redis.
  4. Välj fliken Nätverk eller välj knappen Nätverk längst ned på sidan.

  5. fliken Nätverk väljer du din anslutningsmetod.

  6. Välj fliken Nästa: Avancerat eller välj knappen Nästa: Avancerat längst ned på sidan.

  7. På fliken Avancerat för en basic- eller standard-cacheinstans väljer du aktivera om du vill aktivera en icke-TLS-port. Du kan också välja vilken Redis-version du vill använda, antingen 4 eller 6.

    Redis version 4 eller 6.

  8. På fliken Avancerat för premiumcacheinstansen konfigurerar du inställningarna för icke-TLS-port, klustring och datapersistence. Du kan också välja vilken Redis-version du vill använda, antingen 4 eller 6.

  9. Välj fliken Nästa: Taggar eller välj knappen Nästa: Taggar längst ned på sidan.

  10. Du kan också ange namn och värde på fliken Taggar om du vill kategorisera resursen.

  11. Välj Granska + skapa. Du kommer till fliken Granska + skapa där Azure verifierar din konfiguration.

  12. När det gröna meddelandet Valideringen har godkänts visas väljer du Skapa.

Det tar en stund innan cacheminnet skapas. Du kan övervaka förloppet på Azure Cache for Redis översiktssidan. När Status visas som Körs är cacheminnet klart att användas.

Hämta värdnamn, portar och åtkomstnycklar från Azure Portal

För att kunna ansluta Azure Cache for Redis en instans behöver cacheklienter värdnamnet, portarna och en nyckel för cachen. Vissa klienter kan hänvisa till dessa objekt med namn som skiljer sig något. Du kan hämta värdnamn, portar och nycklar från Azure Portal.

  • Om du vill hämta åtkomstnycklarna går du till det vänstra navigeringsfönstret i cacheminnet och väljer Åtkomstnycklar.

    Nycklar för Azure Cache for Redis

  • Om du vill hämta värdnamnet och portarna går du till det vänstra navigeringsfönstret i cacheminnet och väljer Egenskaper. Värdnamnet har formen <DNS name> .redis.cache.windows.net.

    Egenskaper för Azure Cache for Redis

Anteckna VÄRDNAMN och den Primära åtkomstnyckeln. Du använder dessa värden senare för att konstruera CacheConnection-hemligheten.

Skapa en konsolapp

Öppna ett nytt kommandofönster och kör följande kommando för att skapa en ny .NET Core-konsolapp:

dotnet new console -o Redistest

I ditt kommandofönster, ändrar du till den nya Redistest-projektkatalogen.

Lägg till Secret Manager i projektet

I det här avsnittet lägger du till verktyget Secret Manager i projektet. Verktyget Secret Manager lagrar känsliga uppgifter för utvecklingsarbete utanför projektträdet. Den här metoden hjälper till att förhindra oavsiktlig delning av apphemligheter i källkoden.

Öppna din Redistest.csproj-fil. Lägg till ett DotNetCliToolReference-element att inkludera Microsoft.Extensions.SecretManager.Tools. Lägg också till ett UserSecretsId-elementet enligt nedan och spara filen.

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
        <UserSecretsId>Redistest</UserSecretsId>
    </PropertyGroup>
    <ItemGroup>
        <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
    </ItemGroup>
</Project>

Kör följande kommando för att lägga till paketet Microsoft.Extensions.Configuration.UserSecrets i projektet:

dotnet add package Microsoft.Extensions.Configuration.UserSecrets

Kör följande kommando för att återställa dina paket:

dotnet restore

I ditt kommandofönster, kör du följande kommandon för att lagra en ny hemlighet med namnet CacheConnection efter att du ersätter platshållarna (inklusive hakparenteser) för ditt cachenamn och primära åtkomstnyckel:

dotnet user-secrets set CacheConnection "<cache name>.redis.cache.windows.net,abortConnect=false,ssl=true,allowAdmin=true,password=<primary-access-key>"

Lägg till följande using-uttryck i Program.cs:

using Microsoft.Extensions.Configuration;

Lägg till följande medlemmar till Program-klassen i Program.cs. Den här koden initierar en konfiguration för att komma åt användarhemligheten för Azure Cache for Redis-anslutningssträngen.

private static IConfigurationRoot Configuration { get; set; }
const string SecretName = "CacheConnection";

private static void InitializeConfiguration()
{
    var builder = new ConfigurationBuilder()
        .AddUserSecrets<Program>();

    Configuration = builder.Build();
}

Konfigurera cacheklienten

I det här avsnittet konfigurerar du konsolprogrammet att använda StackExchange.Redis-klienten för .NET.

I kommandofönstret, kör du följande kommando i projektkatalogen Redistest:

dotnet add package StackExchange.Redis

När installationen är klar är StackExchange.Redis-cacheklienten tillgänglig för användning med ditt projekt.

Ansluta till cachen

Lägg till följande using-uttryck i Program.cs:

using StackExchange.Redis;

Anslutningen till Azure Cache for Redis hanteras av ConnectionMultiplexer-klassen. Den här klassen delas och återanvändas i hela klientprogrammet. Skapa inte en ny anslutning för varje åtgärd.

I Program.cs, lägger du till följande medlemmar i Program-klassen för ditt konsolprogram:

private static Lazy<ConnectionMultiplexer> lazyConnection = CreateConnection();

public static ConnectionMultiplexer Connection
{
    get
    {
        return lazyConnection.Value;
    }
}

private static Lazy<ConnectionMultiplexer> CreateConnection()
{
    return new Lazy<ConnectionMultiplexer>(() =>
    {
        string cacheConnection = Configuration[SecretName];
        return ConnectionMultiplexer.Connect(cacheConnection);
    });
}

Den här metoden för att dela en ConnectionMultiplexer-instans i ditt program använder en statisk egenskap som returnerar en ansluten instans. Koden ger ett trådsäkert sätt att endast initiera en enda ansluten ConnectionMultiplexer-instans. abortConnect är inställt på falskt, vilket innebär att anropet lyckas även om en anslutning till Azure Cache for Redis inte har etablerats. En viktig egenskap i ConnectionMultiplexer är att anslutningen till cachen återställs automatiskt när nätverksproblemet eller andra fel har åtgärdats.

Du kommer åt värdet för CacheConnection-hemligheten med konfigurationsprovidern för hemlighetshanteraren och används som lösenordsparametern.

Hantera RedisConnectionException och SocketException genom att ansluta igen

En rekommenderad metod när du anropar metoder på är att försöka lösa undantag automatiskt genom att stänga och ConnectionMultiplexer RedisConnectionException återupprätta SocketException anslutningen.

Lägg till följande using-instruktioner i Program.cs:

using System.Net.Sockets;
using System.Threading;

Lägg till följande medlemmar i klassen i Program.cs: Program

private static IConfigurationRoot Configuration { get; set; }
private static long _lastReconnectTicks = DateTimeOffset.MinValue.UtcTicks;
private static DateTimeOffset _firstErrorTime = DateTimeOffset.MinValue;
private static DateTimeOffset _previousErrorTime = DateTimeOffset.MinValue;
private static SemaphoreSlim _reconnectSemaphore = new SemaphoreSlim(initialCount: 1, maxCount: 1);
private static SemaphoreSlim _initSemaphore = new SemaphoreSlim(initialCount: 1, maxCount: 1);
private static ConnectionMultiplexer _connection;
private static bool _didInitialize = false;
// In general, let StackExchange.Redis handle most reconnects,
// so limit the frequency of how often ForceReconnect() will
// actually reconnect.
public static TimeSpan ReconnectMinInterval => TimeSpan.FromSeconds(60);
// If errors continue for longer than the below threshold, then the
// multiplexer seems to not be reconnecting, so ForceReconnect() will
// re-create the multiplexer.
public static TimeSpan ReconnectErrorThreshold => TimeSpan.FromSeconds(30);
public static TimeSpan RestartConnectionTimeout => TimeSpan.FromSeconds(15);
public static int RetryMaxAttempts => 5;

public static ConnectionMultiplexer Connection { get { return _connection; } }
private static async Task InitializeAsync()
{
    if (_didInitialize)
    {
        throw new InvalidOperationException("Cannot initialize more than once.");
    }
    var builder = new ConfigurationBuilder()
        .AddUserSecrets<Program>();
    Configuration = builder.Build();
    _connection = await CreateConnectionAsync();
    _didInitialize = true;
}
// This method may return null if it fails to acquire the semaphore in time.
// Use the return value to update the "connection" field
private static async Task<ConnectionMultiplexer> CreateConnectionAsync()
{
    if (_connection != null)
    {
        // If we already have a good connection, let's re-use it
        return _connection;
    }
    try
    {
        await _initSemaphore.WaitAsync(RestartConnectionTimeout);
    }
    catch
    {
        // We failed to enter the semaphore in the given amount of time. Connection will either be null, or have a value that was created by another thread.
        return _connection;
    }
    // We entered the semaphore successfully.
    try
    {
        if (_connection != null)
        {
            // Another thread must have finished creating a new connection while we were waiting to enter the semaphore. Let's use it
            return _connection;
        }
        // Otherwise, we really need to create a new connection.
        string cacheConnection = Configuration["CacheConnection"].ToString();
        return await ConnectionMultiplexer.ConnectAsync(cacheConnection);
    }
    finally
    {
        _initSemaphore.Release();
    }
}
private static async Task CloseConnectionAsync(ConnectionMultiplexer oldConnection)
{
    if (oldConnection == null)
    {
        return;
    }
    try
    {
        await oldConnection.CloseAsync();
    }
    catch (Exception)
    {
        // Ignore any errors from the oldConnection
    }
}
/// <summary>
/// Force a new ConnectionMultiplexer to be created.
/// NOTES:
///     1. Users of the ConnectionMultiplexer MUST handle ObjectDisposedExceptions, which can now happen as a result of calling ForceReconnectAsync().
///     2. Call ForceReconnectAsync() for RedisConnectionExceptions and RedisSocketExceptions. You can also call it for RedisTimeoutExceptions,
///         but only if you're using generous ReconnectMinInterval and ReconnectErrorThreshold. Otherwise, establishing new connections can cause
///         a cascade failure on a server that's timing out because it's already overloaded.
///     3. The code will:
///         a. wait to reconnect for at least the "ReconnectErrorThreshold" time of repeated errors before actually reconnecting
///         b. not reconnect more frequently than configured in "ReconnectMinInterval"
/// </summary>
public static async Task ForceReconnectAsync()
{
    var utcNow = DateTimeOffset.UtcNow;
    long previousTicks = Interlocked.Read(ref _lastReconnectTicks);
    var previousReconnectTime = new DateTimeOffset(previousTicks, TimeSpan.Zero);
    TimeSpan elapsedSinceLastReconnect = utcNow - previousReconnectTime;
    // If multiple threads call ForceReconnectAsync at the same time, we only want to honor one of them.
    if (elapsedSinceLastReconnect < ReconnectMinInterval)
    {
        return;
    }
    try
    {
        await _reconnectSemaphore.WaitAsync(RestartConnectionTimeout);
    }
    catch
    {
        // If we fail to enter the semaphore, then it is possible that another thread has already done so.
        // ForceReconnectAsync() can be retried while connectivity problems persist.
        return;
    }
    try
    {
        utcNow = DateTimeOffset.UtcNow;
        elapsedSinceLastReconnect = utcNow - previousReconnectTime;
        if (_firstErrorTime == DateTimeOffset.MinValue)
        {
            // We haven't seen an error since last reconnect, so set initial values.
            _firstErrorTime = utcNow;
            _previousErrorTime = utcNow;
            return;
        }
        if (elapsedSinceLastReconnect < ReconnectMinInterval)
        {
            return; // Some other thread made it through the check and the lock, so nothing to do.
        }
        TimeSpan elapsedSinceFirstError = utcNow - _firstErrorTime;
        TimeSpan elapsedSinceMostRecentError = utcNow - _previousErrorTime;
        bool shouldReconnect =
            elapsedSinceFirstError >= ReconnectErrorThreshold // Make sure we gave the multiplexer enough time to reconnect on its own if it could.
            && elapsedSinceMostRecentError <= ReconnectErrorThreshold; // Make sure we aren't working on stale data (e.g. if there was a gap in errors, don't reconnect yet).
        // Update the previousErrorTime timestamp to be now (e.g. this reconnect request).
        _previousErrorTime = utcNow;
        if (!shouldReconnect)
        {
            return;
        }
        _firstErrorTime = DateTimeOffset.MinValue;
        _previousErrorTime = DateTimeOffset.MinValue;
        ConnectionMultiplexer oldConnection = _connection;
        await CloseConnectionAsync(oldConnection);
        _connection = null;
        _connection = await CreateConnectionAsync();
        Interlocked.Exchange(ref _lastReconnectTicks, utcNow.UtcTicks);
    }
    finally
    {
        _reconnectSemaphore.Release();
    }
}
// In real applications, consider using a framework such as
// Polly to make it easier to customize the retry approach.
private static async Task<T> BasicRetryAsync<T>(Func<T> func)
{
    int reconnectRetry = 0;
    int disposedRetry = 0;
    while (true)
    {
        try
        {
            return func();
        }
        catch (Exception ex) when (ex is RedisConnectionException || ex is SocketException)
        {
            reconnectRetry++;
            if (reconnectRetry > RetryMaxAttempts)
                throw;
            await ForceReconnectAsync();
        }
        catch (ObjectDisposedException)
        {
            disposedRetry++;
            if (disposedRetry > RetryMaxAttempts)
                throw;
        }
    }
}
public static Task<IDatabase> GetDatabaseAsync()
{
    return BasicRetryAsync(() => Connection.GetDatabase());
}
public static Task<System.Net.EndPoint[]> GetEndPointsAsync()
{
    return BasicRetryAsync(() => Connection.GetEndPoints());
}
public static Task<IServer> GetServerAsync(string host, int port)
{
    return BasicRetryAsync(() => Connection.GetServer(host, port));
}

Kör cachekommandon

I Program.cs, lägger du till följande kod för Main-proceduren i Program-klassen för ditt konsolprogram:

static void Main(string[] args)
{
    InitializeConfiguration();

    IDatabase cache = GetDatabase();

    // Perform cache operations using the cache object...

    // Simple PING command
    string cacheCommand = "PING";
    Console.WriteLine("\nCache command  : " + cacheCommand);
    Console.WriteLine("Cache response : " + cache.Execute(cacheCommand).ToString());

    // Simple get and put of integral data types into the cache
    cacheCommand = "GET Message";
    Console.WriteLine("\nCache command  : " + cacheCommand + " or StringGet()");
    Console.WriteLine("Cache response : " + cache.StringGet("Message").ToString());

    cacheCommand = "SET Message \"Hello! The cache is working from a .NET Core console app!\"";
    Console.WriteLine("\nCache command  : " + cacheCommand + " or StringSet()");
    Console.WriteLine("Cache response : " + cache.StringSet("Message", "Hello! The cache is working from a .NET Core console app!").ToString());

    // Demonstrate "SET Message" executed as expected...
    cacheCommand = "GET Message";
    Console.WriteLine("\nCache command  : " + cacheCommand + " or StringGet()");
    Console.WriteLine("Cache response : " + cache.StringGet("Message").ToString());

    // Get the client list, useful to see if connection list is growing...
    // Note that this requires allowAdmin=true in the connection string
    cacheCommand = "CLIENT LIST";
    Console.WriteLine("\nCache command  : " + cacheCommand);
    var endpoint = (System.Net.DnsEndPoint)GetEndPoints()[0];
    IServer server = GetServer(endpoint.Host, endpoint.Port);
    ClientInfo[] clients = server.ClientList();

    Console.WriteLine("Cache response :");
    foreach (ClientInfo client in clients)
    {
        Console.WriteLine(client.Raw);
    }

    CloseConnection(lazyConnection);
}

Spara Program.cs.

Azure Cache for Redis har ett konfigurerbart antal databaser (16 är standard) som kan användas för att logiskt separera data i ett Azure Cache for Redis. Koden ansluter till standarddatabasen DB 0. Mer information finns i What are Redis databases? (Vad är Redis-databaser?) och Default Redis server configuration (Standardkonfiguration av Redis-server).

Cacheobjekt kan lagras och hämtas med hjälp av metoderna StringSet och StringGet.

Redis lagrar de flesta data som Redis-strängar, men dessa strängar kan innehålla flera typer av data, inklusive serialiserade binära data som kan användas när .NET-objekt lagras i cacheminnet.

Kör följande kommando i ditt kommandofönster för att skapa appen:

dotnet build

Kör sedan appen med följande kommando:

dotnet run

I exemplet nedan ser du att Message-nyckeln tidigare hade ett cachelagrat värde som angavs med Redis-konsolen i Azure Portal. Appen uppdatera det cachelagrade värdet. Appen körde även kommandona PING och CLIENT LIST.

Partiell konsolapp

Arbeta med .NET-objekt i cachen

Azure Cache for Redis kan cachelagra både .NET-objekt och basdatatyper, men .NET-objekt måste serialiseras innan de kan cachelagras. Den här .NET-objektserialiseringen är programutvecklarens ansvar och ger utvecklaren flexibilitet i valet av serialiserare.

Ett enkelt sätt att serialisera objekt är att använda JsonConvert-serialiseringsmetoderna i Newtonsoft.Json och serialisera till och från JSON. I det här avsnittet ska du lägga till ett .NET-objekt till cachen.

Kör följande kommando för att lägga till Newtonsoft.json-paketet till appen:

dotnet add package Newtonsoft.json

Lägg till följande using-uttryck högst upp i Program.cs:

using Newtonsoft.Json;

Lägg till följande Employee-klassdefinition i Program.cs:

class Employee
{
    public string Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }

    public Employee(string employeeId, string name, int age)
    {
        Id = employeeId;
        Name = name;
        Age = age;
    }
}

Längst ned i Main()-procedur i Program.cs, och innan anropet till CloseConnection(), lägger du till följande rader med kod i cachen och hämtar ett serialiserat .NET-objekt:

    // Store .NET object to cache
    Employee e007 = new Employee("007", "Davide Columbo", 100);
    Console.WriteLine("Cache response from storing Employee .NET object : " + 
    cache.StringSet("e007", JsonConvert.SerializeObject(e007)));

    // Retrieve .NET object from cache
    Employee e007FromCache = JsonConvert.DeserializeObject<Employee>(cache.StringGet("e007"));
    Console.WriteLine("Deserialized Employee .NET object :\n");
    Console.WriteLine("\tEmployee.Name : " + e007FromCache.Name);
    Console.WriteLine("\tEmployee.Id   : " + e007FromCache.Id);
    Console.WriteLine("\tEmployee.Age  : " + e007FromCache.Age + "\n");

Spara Program.cs och återskapa appen med följande kommando:

dotnet build

Kör appen med följande kommando för att testa serialisering av .NET-objekt:

dotnet run

Konsolappen har slutförts

Rensa resurser

Om du ska fortsätta till nästa självstudie kan du behålla resurserna som du har skapat i den här självstudien och använda dem igen.

Om du är klar med exempelappen för snabbstart kan du ta bort Azure-resurserna som du skapade i snabbstarten för att undvika kostnader.

Viktigt

Det går inte att ångra borttagningen av en resursgrupp och att resursgruppen och alla resurser i den tas bort permanent. Kontrollera att du inte av misstag tar bort fel resursgrupp eller resurser. Om du har skapat resurserna som är värdar för det här exemplet i en befintlig resursgrupp som innehåller resurser som du vill behålla, kan du ta bort varje resurs individuellt till vänster i stället för att ta bort resursgruppen.

Logga in på Azure Portal och välj Resursgrupper.

Skriv namnet på din resursgrupp i textrutan Filter by name... (Filtrera efter namn...). Anvisningarna för den här artikeln använde en resursgrupp med namnet TestResources. I din resursgrupp i resultatlistan väljer du ... och sedan Ta bort resursgrupp.

Ta bort

Du blir ombedd att bekräfta borttagningen av resursgruppen. Skriv namnet på resursgruppen för att bekräfta och välj Ta bort.

Efter en liten stund tas resursgruppen och resurser som finns i den bort.

Nästa steg

I den här snabbstarten har du lärt dig att använda Azure Cache for Redis från ett .NET Core-program. Fortsätt till nästa snabbstart om du vill använda Azure Cache for Redis med en ASP.NET-webbapp.

Vill du optimera och spara på dina molnutgifter?