Inicio rápido: Uso de Azure Cache for Redis con .NET Framework

En este inicio rápido incorporará Azure Redis Cache en una aplicación .NET Framework para acceder a una caché dedicada y segura, a la que se puede acceder desde cualquier aplicación de Azure. Concretamente, usará el cliente StackExchange.Redis con código C# en una aplicación de consola de .NET.

Ir al código en GitHub

Si quiere pasar directamente al código, consulte Inicio rápido: Uso de Azure Cache for Redis en .NET Framework en GitHub.

Requisitos previos

Creación de una caché

  1. Para crear una instancia de caché, inicie sesión en Azure Portal y seleccione Crear un recurso.

    Create a resource is highlighted in the left navigation pane.

  2. En la página Nuevo, seleccione Base de datos y, a continuación, seleccione Azure Cache for Redis.

    On New, Databases is highlighted, and Azure Cache for Redis is highlighted.

  3. En la página Nueva instancia de Redis Cache, configure las opciones de la nueva caché.

    Configuración Elegir un valor Descripción
    Suscripción Desplácese hacia abajo y seleccione su suscripción. La suscripción en la que se creará esta nueva instancia de Azure Cache for Redis.
    Grupos de recursos Desplácese hacia abajo y seleccione un grupo de recursos o Crear nuevo y escriba un nombre nuevo para el grupo de recursos. Nombre del grupo de recursos en el que se van a crear la caché y otros recursos. Al colocar todos los recursos de la aplicación en un grupo de recursos, puede administrarlos o eliminarlos fácilmente.
    Nombre DNS Escriba un nombre único. El nombre de la memoria caché debe ser una cadena de entre 1 y 63 caracteres, y solo puede contener números, letras o guiones. El nombre debe comenzar y terminar por un número o una letra y no puede contener guiones consecutivos. El nombre de host de la instancia de caché será nombre DNS.redis.cache.windows.net.
    Ubicación Desplácese hacia abajo y seleccione una ubicación. Seleccione una región cerca de otros servicios que vayan a usar la memoria caché.
    Tipo de caché Desplácese hacia abajo y seleccione un nivel. El nivel determina el tamaño, rendimiento y características disponibles para la memoria caché. Para más información, consulte la introducción a Azure Redis Cache.
  4. Seleccione la pestaña Redes o elija el botón Redes situado en la parte inferior de la página.

  5. En la pestaña Redes, seleccione el método de conectividad.

  6. Seleccione la pestaña Siguiente: Opciones avanzadas o seleccione el botón Siguiente: Opciones avanzadas en la parte inferior de la página.

  7. En la pestaña Opciones avanzadas de una instancia de caché básica o estándar, seleccione el botón de alternancia de habilitación si desea habilitar un puerto que no sea TLS. También puede seleccionar la versión de Redis que quiere usar, 4 o 6.

    Redis version 4 or 6.

  8. En la pestaña Opciones avanzadas de la instancia de caché Premium, configure el puerto no TLS, la agrupación en clústeres y la persistencia de datos. También puede seleccionar la versión de Redis que quiere usar, 4 o 6.

  9. Seleccione el botón Siguiente: Opciones avanzadas o elija el botón Siguiente: Etiquetas situado en la parte inferior de la página.

  10. Opcionalmente, en la pestaña Etiquetas, escriba el nombre y el valor si desea clasificar el recurso.

  11. Seleccione Revisar + crear. Pasará a la pestaña Revisar y crear, donde Azure validará la configuración.

  12. Tras aparecer el mensaje verde Validación superada, seleccione Crear.

La caché tarda un tiempo en crearse. Puede supervisar el progreso en la página Información general de Azure Cache for Redis. Cuando Estado se muestra como En ejecución, la memoria caché está lista para su uso.

Recuperación del nombre de host, los puertos y las claves de acceso desde Azure Portal

Para conectar con una instancia de Azure Cache for Redis, los clientes de dicha caché necesitan el nombre de host, los puertos y una clave para la caché. Es posible que algunos clientes utilicen nombres ligeramente diferentes para estos elementos. Puede obtener el nombre de host, los puertos y las claves de Azure Portal.

  • Para obtener las claves de acceso, en el panel de navegación izquierdo de Azure Cache for Redis, seleccione Claves de acceso.

    Azure Cache for Redis keys

  • Para obtener el nombre de host y los puertos, en el panel de navegación izquierdo de Azure Cache for Redis, seleccione Propiedades. El nombre de host tiene el formato nombre_DNS>.redis.cache.windows.net.

    Azure Cache for Redis properties

Cree un archivo en el equipo llamado CacheSecrets.config y colóquelo en una ubicación donde no se vaya a insertar en el repositorio con el código fuente de la aplicación de ejemplo. En esta guía de inicio rápido, el archivo CacheSecrets.config se encuentra aquí, C:\AppSecrets\CacheSecrets.config.

Edite el archivo CacheSecrets.config y agregue el contenido siguiente:

<appSettings>
    <add key="CacheConnection" value="<host-name>,abortConnect=false,ssl=true,allowAdmin=true,password=<access-key>"/>
</appSettings>

Reemplace <host-name> por su nombre de host de caché.

Reemplace <access-key> por la clave principal de la caché.

Creación de una aplicación de consola

Abra Visual Studio, seleccione ArchivoNuevoProyecto.

Seleccione Aplicación de consola (.NET Framework) y Siguiente para configurar la aplicación. Escriba un nombre de proyecto, compruebe que está seleccionado .NET Framework 4.6.1 o una versión posterior y, a continuación, seleccione Crear para crear una aplicación de consola.

Configuración del cliente de caché

En esta sección, configurará la aplicación de consola para utilizar el cliente StackExchange.Redis para .NET.

En Visual Studio, seleccione HerramientasAdministrador de paquetes NuGetConsola del Administrador de paquetes y ejecute el siguiente comando desde la ventana de la consola del administrador de paquetes.

Install-Package StackExchange.Redis

Una vez completada la instalación, el cliente de caché StackExchange.Redis está disponible para su uso con el proyecto.

Conexión a la memoria caché

En Visual Studio, abra el archivo App.config actualícelo para incluir un atributo file que hace referencia al archivo file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>

    <appSettings file="C:\AppSecrets\CacheSecrets.config"></appSettings>
</configuration>

En el Explorador de soluciones, haga clic con el botón derecho en Referencias y seleccione Agregar referencia. Agregue una referencia al ensamblado System.Configuration.

Agregue las siguientes instrucciones using a using:

using StackExchange.Redis;
using System.Configuration;

La clase ConnectionMultiplexer administra la conexión con Azure Redis Cache. Esta clase debe compartirse y reutilizarse en toda la aplicación cliente. No cree una nueva conexión para cada operación.

Nunca almacene credenciales en el código fuente. Para simplificar este ejemplo, solo se usa un archivo de configuración externo llamado secrets. Un enfoque más adecuado sería utilizar Azure Key Vault con certificados.

En Program.cs, agregue los siguientes miembros a la clase de la aplicación de consola:

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 = ConfigurationManager.AppSettings["CacheConnection"].ToString();
        return ConnectionMultiplexer.Connect(cacheConnection);
    });
}

Este enfoque para compartir una instancia de ConnectionMultiplexer en la aplicación usa una propiedad estática que devuelve una instancia conectada. El código proporciona una manera segura para subprocesos de inicializar solo una instancia de ConnectionMultiplexer conectada. abortConnect está establecido en false, lo que significa que la llamada se realizará correctamente incluso si no se establece ninguna conexión a Azure Redis Cache. Una de las características principales de ConnectionMultiplexer es que restaura automáticamente la conectividad a la caché una vez que el problema de red u otras causas se resuelven.

El valor de la configuración de aplicación CacheConnection se utiliza para hacer referencia a la cadena de conexión de la caché de Azure Portal como el parámetro de contraseña.

Control de RedisConnectionException y SocketException mediante reconexión

Un procedimiento recomendado a la hora de llamar a los métodos de ConnectionMultiplexer es intentar resolver las excepciones RedisConnectionException y SocketException automáticamente cerrando y reestableciendo la conexión.

Agregue las siguientes instrucciones using a using:

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

En Program.cs, agregue los siguientes miembros a la clase :

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.");
    }
    _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 = ConfigurationManager.AppSettings["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));
}

Ejecución de comandos de caché

Agregue el código siguiente al procedimiento Main de la clase Program de la aplicación de consola:

static void Main(string[] args)
{
    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 console app!\"";
    Console.WriteLine("\nCache command  : " + cacheCommand + " or StringSet()");
    Console.WriteLine("Cache response : " + cache.StringSet("Message", "Hello! The cache is working from a .NET 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);
}

Las instancias de Azure Cache for Redis tienen un número configurable de bases de datos (valor predeterminado de 16) que se pueden usar para separar de forma lógica los datos dentro de una instancia de Azure Redis Cache. El código se conecta a la base de datos predeterminada, DB 0. Para más información, consulte What are Redis databases? (¿Qué son las bases de datos de Redis?) y Configuración predeterminada del servidor Redis.

Los elementos en la memoria caché se pueden almacenar y recuperar mediante los métodos StringSet y StringGet.

Redis almacena la mayoría de los datos como cadenas Redis, pero estas cadenas pueden contener muchos tipos de datos, como por ejemplo datos binarios serializados, que se pueden usar cuando se almacenan objetos .NET en caché.

Presione Ctrl+F5 para compilar y ejecutar la aplicación de consola.

En el ejemplo siguiente, puede ver que la clave Message tenía anteriormente un valor almacenado en caché, que se estableció mediante la Consola de Redis en Azure Portal. La aplicación actualizó ese valor almacenado en caché. La aplicación también ejecutó los comandos PING y CLIENT LIST.

Console app partial

Trabajar con objetos .NET en la memoria caché

Azure Redis Cache puede almacenar en caché objetos .NET así como tipos de datos primitivos, pero antes de poder almacenar en caché un objeto .NET, se debe serializar. La serialización del objeto .NET es responsabilidad del desarrollador de la aplicación, que tiene total flexibilidad a la hora de elegir el serializador.

Una manera sencilla para serializar objetos es usar los métodos de serialización JsonConvert de JsonConvert y serializar a y desde JSON. En esta sección, agregará un objeto .NET a la memoria caché.

En Visual Studio, seleccione HerramientasAdministrador de paquetes NuGetConsola del Administrador de paquetes y ejecute el siguiente comando desde la ventana de la consola del administrador de paquetes.

Install-Package Newtonsoft.Json

Agregue la siguiente instrucción using al principio del archivo using:

using Newtonsoft.Json;

Agregue la siguiente definición de clase Employee a Employee:

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

En la parte inferior del procedimiento Main() de Main(), antes de llamar a CloseConnection(), agregue las siguientes líneas de código para almacenar en caché y recuperar un objeto .NET serializado:

    // 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");

Presione Ctrl+F5 para compilar y ejecutar la aplicación de consola para probar la serialización de objetos .NET.

Console app completed

Limpieza de recursos

Si va a seguir con el tutorial siguiente, puede mantener los recursos creados en esta guía de inicio rápido y volverlos a utilizar.

En caso contrario, si ya ha terminado con la aplicación de ejemplo de la guía de inicio rápido, puede eliminar los recursos de Azure creados en este tutorial para evitar cargos.

Importante

La eliminación de un grupo de recursos es irreversible y el grupo de recursos y todos los recursos que contiene se eliminarán de forma permanente. Asegúrese de no eliminar por accidente el grupo de recursos o los recursos equivocados. Si ha creado los recursos para hospedar este ejemplo en un grupo de recursos existente que contiene recursos que quiere conservar, puede eliminar cada recurso individualmente en la parte izquierda, en lugar de eliminar el grupo de recursos.

Inicie sesión en Azure Portal y después seleccione Grupos de recursos.

Escriba el nombre del grupo de recursos en el cuadro de texto Filtrar por nombre... . En las instrucciones de este artículo se usa un grupo de recursos llamado TestResources. En el grupo de recursos de la lista de resultados, seleccione ... y, después, Eliminar grupo de recursos.

Delete

Se le pedirá que confirme la eliminación del grupo de recursos. Escriba el nombre del grupo de recursos para confirmar y seleccione Eliminar.

Transcurridos unos instantes, el grupo de recursos y todos los recursos que contiene se eliminan.

Pasos siguientes

En este inicio rápido, ha aprendido a usar Azure Redis Cache desde una aplicación .NET. Continúe con el siguiente inicio rápido para usar Azure Redis Cache con una aplicación web ASP.NET.

¿Quiere optimizar y ahorrar en el gasto en la nube?