c# - microsoft - redis asp net mvc
StackExchange.Redis con Azure Redis es inusualmente lento o arroja errores de tiempo de espera (5)
Aquí está el patrón recomendado, de la documentación de 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;
}
}
Algunos puntos importantes:
- Utiliza Lazy <T> para manejar la inicialización segura de subprocesos
- Establece "abortConnect = false", lo que significa que si falla el intento de conexión inicial, ConnectionMultiplexer volverá a intentar silenciosamente en el fondo en lugar de arrojar una excepción.
- No comprueba la propiedad IsConnected, ya que ConnectionMultiplexer volverá a intentar automáticamente en el fondo si se corta la conexión.
Moveré todo el uso de caché de Azure In-Role existente a Redis y decidí usar la vista previa de Azure Redis junto con la biblioteca StackExchange.Redis ( https://github.com/StackExchange/StackExchange.Redis ). Escribí todo el código sin muchos problemas, pero cuando lo ejecuto es absolutamente inútilmente lento y arroja constantemente errores de tiempo de espera (mi tiempo de espera se establece en 15 segundos).
Aquí está el código relevante de cómo estoy configurando la conexión Redis y usándola para operaciones 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;
}
La ejecución de comandos muy simples como Get y Set a menudo se agota o toma de 5 a 10 segundos para completarse. Parece que niega todo el propósito de usarlo como un caché si es MUCHO más lento que en realidad recuperar los datos reales de mi base de datos :)
¿Estoy haciendo algo obviamente incorrecto?
Editar : aquí hay algunas estadísticas que sacó del servidor (usando Redis Desktop Manager) en caso de que arroje algo de luz sobre cualquier cosa.
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:
Edición 2 : Aquí están los métodos de extensión que estoy usando para Get / Set: son métodos muy simples que simplemente convierten un objeto en JSON y llaman a 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);
}
Edición 3 : aquí hay un par de ejemplos de mensajes de error:
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
El problema es cómo el objeto de conexión creado y utilizado. primero nos enfrentamos al problema exacto y lo solucionamos con un único objeto de conexión que se usa en todas las solicitudes web. Y comprobamos que es nulo o está conectado en el inicio de la sesión para volver a crear un objeto elegante. eso solucionó el problema
Nota: También compruebe en qué zona de Azure se está ejecutando su instancia de Redis Cache y qué zona existe su servidor web. Es mejor mantener ambos en la misma zona
En el archivo 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();
}
}
En nuestro caso, el problema es cuando se usa una conexión SSL. Está mostrando que su administrador de escritorio se está ejecutando en el puerto no SSL, pero su código usa SSL.
Un punto de referencia rápido en nuestro redis Azure sin SSL, recuperando alrededor de 80k valores con un comando LRANGE (también con .net y StackExchange.Redis) es básicamente instantáneo. Cuando se utiliza SSL, la misma consulta lleva 27 segundos.
Aplicación web: estándar S2
Redis: Estándar 1 GB
Editar: Al revisar el SLOWLOG, Redis parece que realmente acelera el registro lento con su tiempo de 14ms o más para tomar las filas, pero esto está lejos de la transferencia real con SSL habilitado. Terminamos con un Redis premium para tener algún tipo de seguridad entre Redis y Web Apps.
Estaba teniendo problemas similares. El caché de Redis era inusualmente lento, pero definitivamente estaba en caché. En algunos casos, tardaron entre 20 y 40 segundos en cargar una página.
Me di cuenta de que el servidor de caché estaba en una ubicación diferente a la del sitio. Actualicé el servidor de caché para vivir en la misma ubicación que el sitio web y ahora todo funciona como se esperaba.
Esa misma página ahora se carga en 4-6 segundos.
Buena suerte a cualquier otra persona que tenga estos problemas.
Funcionó en mi caso. No olvides aumentar SyncTimeout. El valor predeterminado es 1 segundo.
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);
});
Compruebe si es Azure Redis Cache y el Cliente en la misma región en Azure. Por ejemplo, es posible que obtenga tiempos de espera excedidos cuando su caché está en el este de EE. UU. Pero el cliente se encuentra en el oeste de EE. UU. Y la solicitud no finaliza en tiempo de sincronización, o puede que esté excediendo el tiempo de depuración desde su equipo de desarrollo local. Se recomienda encarecidamente tener el caché y el cliente en la misma región de Azure. Si tiene un escenario para hacer una llamada de región cruzada, querrá establecer el tiempo sin conexión a un valor más alto.