web-dev-qa-db-fra.com

StackExchange.Redis avec Azure Redis est anormalement lent ou génère des erreurs d'expiration

Je déplace toute mon utilisation du cache Azure In-Role vers Redis et j'ai décidé d'utiliser l'aperçu Azure Redis avec la bibliothèque StackExchange.Redis ( https://github.com/StackExchange/StackExchange.Redis ). J'ai écrit tout le code pour cela sans trop de problème, mais lors de son exécution, il est absolument incroyablement lent et génère constamment des erreurs de temporisation (ma période de temporisation est fixée à 15 secondes).

Voici le code pertinent pour la configuration de la connexion Redis et son utilisation pour des opérations simples:

    private static ConnectionMultiplexer _cacheService;
    private static IDatabase _database;
    private static object _lock = new object();

    private void Initialize()
    {
        if (_cacheService == null)
        {
            lock (_lock)
            {
                if (_cacheService == null)
                {
                    var options = new ConfigurationOptions();
                    options.EndPoints.Add("{my url}", 6380);
                    options.Ssl = true;
                    options.Password = "my password";
                    // needed for FLUSHDB command
                    options.AllowAdmin = true;

                    // necessary?
                    options.KeepAlive = 30;
                    options.ConnectTimeout = 15000;
                    options.SyncTimeout = 15000;

                    int database = 0;

                    _cacheService = ConnectionMultiplexer.Connect(options);
                    _database = _cacheService.GetDatabase(database);
                }
            }
        }

    }

    public void Set(string key, object data, TimeSpan? expiry = null)
    {
        if (_database != null)
        {
            _database.Set(key, data, expiry: expiry);
        }
    }

    public object Get(string key)
    {
        if (_database != null)
        {
            return _database.Get(key);
        }
        return null;
    }

L'exécution de commandes très simples comme Get et Set arrive souvent à expiration ou prend 5 à 10 secondes. On dirait que cela annule en quelque sorte le but de l'utiliser comme cache s'il est BEAUCOUP plus lent que de récupérer réellement les données réelles de ma base de données :)

Suis-je en train de faire quelque chose de manifestement incorrect?

Edit : voici quelques statistiques que j'ai extraites du serveur (en utilisant Redis Desktop Manager) au cas où cela éclairerait quelque chose.

Server
redis_version:2.8.12
redis_mode:standalone
os:Windows  
Arch_bits:64
multiplexing_api:winsock_IOCP
gcc_version:0.0.0
process_id:2876

tcp_port:6379
uptime_in_seconds:109909
uptime_in_days:1
hz:10
lru_clock:16072421
config_file:C:\Resources\directory\xxxx.Kernel.localStore\1\redis_2092_port6379.conf

Clients
connected_clients:5
client_longest_output_list:0
client_biggest_input_buf:0
client_total_writes_outstanding:0
client_total_sent_bytes_outstanding:0
blocked_clients:0

Memory
used_memory:4256488
used_memory_human:4.06M
used_memory_rss:67108864
used_memory_rss_human:64.00M
used_memory_peak:5469760
used_memory_peak_human:5.22M
used_memory_lua:33792
mem_fragmentation_ratio:15.77
mem_allocator:dlmalloc-2.8

Persistence
loading:0
rdb_changes_since_last_save:72465
rdb_bgsave_in_progress:0
rdb_last_save_time:1408471440
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok

Stats
total_connections_received:25266
total_commands_processed:123389
instantaneous_ops_per_sec:10
bytes_received_per_sec:275
bytes_sent_per_sec:65
bytes_received_per_sec_human:

Edit 2 : Voici les méthodes d'extension que j'utilise pour Get/Set - ce sont des méthodes très simples qui transforment simplement un objet en JSON et appellent StringSet.

    public static object Get(this IDatabase cache, string key)
    {
        return DeserializeJson<object>(cache.StringGet(key));
    }

    public static void Set(this IDatabase cache, string key, object value, TimeSpan? expiry = null)
    {
        cache.StringSet(key, SerializeJson(value), expiry: expiry);
    }

Edit 3 : voici quelques exemples de messages d'erreur:

    A first chance exception of type 'System.TimeoutException' occurred in StackExchange.Redis.dll
    Timeout performing GET MyCachedList, inst: 11, queue: 1, qu=1, qs=0, qc=0, wr=0/1, in=0/0

    A first chance exception of type 'System.TimeoutException' occurred in StackExchange.Redis.dll
    Timeout performing GET MyCachedList, inst: 1, queue: 97, qu=0, qs=97, qc=0, wr=0/0, in=3568/0
29
user2719100

Voici le modèle recommandé, à partir de la documentation Azure Redis Cache :

private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => {
    return ConnectionMultiplexer.Connect("mycache.redis.cache.windows.net,abortConnect=false,ssl=true,password=...");
});

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

Quelques points importants:

  • Il utilise Lazy <T> pour gérer l'initialisation thread-safe
  • Il définit "abortConnect = false", ce qui signifie que si la tentative de connexion initiale échoue, le ConnectionMultiplexer réessayera silencieusement en arrière-plan plutôt que de lever une exception.
  • Il ne vérifie pas la propriété IsConnected, car ConnectionMultiplexer réessayera automatiquement en arrière-plan si la connexion est interrompue.
22
Mike Harder

J'avais des problèmes similaires. Le cache Redis était anormalement lent mais était définitivement en cache. Dans certains cas, il a fallu 20 à 40 secondes pour charger une page.

J'ai réalisé que le serveur de cache était dans un emplacement différent de celui du site. J'ai mis à jour le serveur de cache pour vivre au même endroit que le site Web et maintenant tout fonctionne comme prévu.

Cette même page se charge désormais en 4 à 6 secondes.

Bonne chance à tous ceux qui ont ces problèmes.

5
christo8989

Le problème est de savoir comment créer et utiliser l'objet de connexion. nous avons d'abord rencontré le problème exact et résolu avec un seul objet de connexion utilisé dans toutes les demandes Web. Et nous vérifions s'il est nul ou connecté au début de la session pour un nouvel objet de création. qui a résolu le problème.

Remarque: Vérifiez également dans quelle zone d'Azure votre instance de Redis Cache s'exécute et quelle zone existe votre serveur Web. Il est préférable de maintenir les deux dans la même zone

Dans le fichier Global.ascx.cs

public static ConnectionMultiplexer RedisConnection;
public static IDatabase RedisCacheDb;

protected void Session_Start(object sender, EventArgs e)
    {
        if (ConfigurationManager.ConnectionStrings["RedisCache"] != null)
        {
            if (RedisConnection == null || !RedisConnection.IsConnected)
            {
                RedisConnection = ConnectionMultiplexer.Connect(ConfigurationManager.ConnectionStrings["RedisCache"].ConnectionString);
            }
            RedisCacheDb = RedisConnection.GetDatabase();
        }
    }
4
Sharat Pandavula

Dans notre cas, le problème est lié à l'utilisation d'une connexion SSL. Vous montrez que votre gestionnaire de bureau s'exécute sur le port non SSL, mais votre code utilise SSL.

Une référence rapide sur nos Azure redis sans SSL, récupérant environ 80k valeurs avec une commande LRANGE (également avec .net et StackExchange.Redis) est fondamentalement instand. Lorsque SSL est utilisé, la même requête prend 27 secondes.

WebApp: Standard S2

Redis: Standard 1 Go

Edit: En vérifiant le SLOWLOG, Redis lui-même semble réellement frapper le slowlog avec son temps de 14 ms environ pour saisir les lignes, mais cela est loin du transfert réel avec SSL activé. Nous nous sommes retrouvés avec un Redis premium pour avoir une sorte de sécurité entre Redis et Web Apps.

3
Crypth

Cela a fonctionné dans mon cas. N'oubliez pas d'augmenter le SyncTimeout. La valeur par défaut est 1 seconde.

private static Lazy<ConnectionMultiplexer> ConnectionMultiplexerItem = new Lazy<ConnectionMultiplexer>(() =>
{
    var redisConfig = ConfigurationOptions.Parse("mycache.redis.cache.windows.net,abortConnect=false,ssl=true,password=...");

    redisConfig.SyncTimeout = 3000;

    return ConnectionMultiplexer.Connect(redisConfig);
});

Vérifiez si vous votre cache Redis Azure et le client dans la même région dans Azure. Par exemple, vous pouvez obtenir des délais d'expiration lorsque votre cache se trouve dans l'Est des États-Unis, mais le client est dans l'Ouest des États-Unis et la demande ne se termine pas dans le délai de synchronisation ou vous pouvez obtenir des délais d'expiration lorsque vous déboguez à partir de votre machine de développement localx. Il est fortement recommandé d'avoir le cache et le client dans la même région Azure. Si vous avez un scénario pour effectuer des appels interrégionaux, vous souhaiterez définir la synchronisation à une valeur plus élevée.

En savoir plus: https://Azure.Microsoft.com/en-us/blog/investigating-timeout-exceptions-in-stackexchange-redis-for-Azure-redis-cache/