Base solution for your next web application
Open Closed

Abp.Runtime.Caching.Redis.AbpRedisCacheDatabaseProvider.GetDatabase() on a clustered redis #12404


User avatar
0
pliaspzero created

Hi,

I've adapted own code for a clusterd inhouse Redis cache - I do not set DefaultDatabase - but I get below error - any hints how to fix?

at Abp.Runtime.Caching.Redis.AbpRedisCacheDatabaseProvider.GetDatabase()

Castle.MicroKernel.ComponentActivator.ComponentActivatorException: ComponentActivator: could not instantiate Abp.Runtime.Caching.Redis.AbpRedisCache
---> StackExchange.Redis.RedisConnectionException: It was not possible to connect to the redis server(s). Error connecting right now. To allow this multiplexer to continue retrying until it's able to connect, use abortConnect=false in your connection string or AbortOnConnectFail=false; in your code.
at StackExchange.Redis.ConnectionMultiplexer.ConnectImpl(ConfigurationOptions configuration, TextWriter writer, Nullable1 serverType, EndPointCollection endpoints) in /_/src/StackExchange.Redis/ConnectionMultiplexer.cs:line 716 at StackExchange.Redis.ConnectionMultiplexer.Connect(ConfigurationOptions configuration, TextWriter log) in /_/src/StackExchange.Redis/ConnectionMultiplexer.cs:line 678 at StackExchange.Redis.ConnectionMultiplexer.Connect(String configuration, TextWriter log) in /_/src/StackExchange.Redis/ConnectionMultiplexer.cs:line 656 at Abp.Runtime.Caching.Redis.AbpRedisCacheDatabaseProvider.CreateConnectionMultiplexer() at System.Lazy1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) at System.Lazy1.CreateValue()
at Abp.Runtime.Caching.Redis.AbpRedisCacheDatabaseProvider.GetDatabase()

My code:
// Laden der Redis-Konfiguration aus den AppSettings
var redisConnectionString = _appConfiguration["Abp:RedisCache:ConnectionString"];
var redisDatabaseId = _appConfiguration.GetValue<int>("Abp:RedisCache:DatabaseId");

// Authentifizierungsdaten für Redis laden
var redisUser = _appConfiguration["Abp:RedisCache:RedisUsername"];
var redisPassword = _appConfiguration["Abp:RedisCache:RedisPassword"];

// Erstellen der Redis-Konfigurationsoptionen
var configurationOptions = ConfigurationOptions.Parse(redisConnectionString);

// SSL/TLS aktivieren für sichere Kommunikation
configurationOptions.Ssl = true;
configurationOptions.AbortOnConnectFail = false; // Verhindert, dass die Verbindung fehlschlägt, wenn Redis vorübergehend nicht erreichbar ist
configurationOptions.ClientName = "SignalRBackplaneWFMOne"; // Identifikationsname für den Redis-Client

// Timeout-Einstellungen zur Vermeidung von Verbindungsproblemen
configurationOptions.ConnectTimeout = 30000; // Maximale Verbindungszeit: 30 Sekunden
configurationOptions.SyncTimeout = 30000; // Maximale Zeit für synchrone Aufrufe: 30 Sekunden
configurationOptions.AsyncTimeout = 30000; // Maximale Zeit für asynchrone Operationen: 30 Sekunden

// Setzen der Authentifizierungsinformationen für Redis, falls konfiguriert
if (!string.IsNullOrEmpty(redisPassword))
{
configurationOptions.Password = redisPassword;
if (!string.IsNullOrEmpty(redisUser))
{
configurationOptions.User = redisUser;
}
}

// Laden des CA-Zertifikats für die SSL-Verbindung
var baseCertPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Certificates");
var caCertFile = _appConfiguration["Abp:RedisCache:CaCertificateFile"];

// Fehlermeldung ausgeben, falls kein Zertifikat in der Konfiguration angegeben ist
if (string.IsNullOrEmpty(caCertFile))
{
throw new InvalidOperationException("CA Certificate file path is missing in configuration.");
}

// Zusammenstellen des vollständigen Zertifikatspfads
var caCertPath = Path.Combine(baseCertPath, caCertFile);

// Überprüfen, ob das CA-Zertifikat existiert
if (!System.IO.File.Exists(caCertPath))
{
throw new FileNotFoundException($"CA Certificate not found: {caCertPath}. Ensure the file exists.");
}

// Laden des CA-Zertifikats aus der PEM-Datei
var caCertificates = new X509Certificate2Collection();
caCertificates.ImportFromPemFile(caCertPath);

// Anpassen der Zertifikatsvalidierung mit einem benutzerdefinierten Trust Store
configurationOptions.CertificateValidation += (sender, cert, chain, errors) =>
{
if (cert != null && chain != null)
{
var cust_chain = new X509Chain();
cust_chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; // Keine Überprüfung der Sperrlisten (Revocation Lists)
cust_chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; // Eigenen Trust Store nutzen
cust_chain.ChainPolicy.CustomTrustStore.Clear();
cust_chain.ChainPolicy.CustomTrustStore.AddRange(caCertificates);

    return cust_chain.Build(new X509Certificate2(cert)); // Validierung des übergebenen Zertifikats
}
return false;

};

// Registrieren von Redis als Backplane für SignalR
try
{
services.AddSignalR().AddStackExchangeRedis(options =>
{
options.Configuration = configurationOptions;
});
}
catch (RedisConnectionException redisEx)
{
// Erstellen einer Logging-Instanz
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.AddDebug();
});
var logger = loggerFactory.CreateLogger<Startup>();

// **Detaillierte Fehlermeldung ausgeben, falls die Redis-Verbindung fehlschlägt**
logger.LogError(redisEx, "Redis connection failed. Exception Details: {Message}", redisEx.Message);
logger.LogError("Connection String: {ConnectionString}, Database ID: {DatabaseId}", redisConnectionString, redisDatabaseId);
logger.LogError("SSL: {Ssl}, SslHost: {SslHost}, AbortOnConnectFail: {AbortOnConnectFail}",
    configurationOptions.Ssl, configurationOptions.SslHost, configurationOptions.AbortOnConnectFail);
logger.LogError("CA Certificate Path: {CaCertPath}", caCertPath);

// **Fällt auf In-Memory SignalR zurück, wenn Redis nicht erreichbar ist**
services.AddSignalR();
logger.LogWarning("Fallback to In-Memory SignalR due to Redis connection failure.");

}


5 Answer(s)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi @pliaspzero

    Can you share the Connection String structure with us after hiding the private fields? It should be in the following structure.

    "Abp": {
      "RedisCache": {
        "ConnectionString": "your.redis.server:6379,password=yourpassword,ssl=True",
        "DatabaseId": 0
      }
    }
    
  • User Avatar
    0
    pliaspzero created

    Hi,

    yes - done like that - I even try this own configuration - still no luck:

                        var redisOptions = new ConfigurationOptions
                        {
                            EndPoints = { "<myServer>redns.redis-cloud.com:18070" },
                            User = "default",
                            Password = "mYPassword",
                            Ssl = true,
                            SslHost = "<myServer>redns.redis-cloud.com",
                            AbortOnConnectFail = false
                        };
    
  • User Avatar
    0
    pliaspzero created

    working:

                        var muxer = ConnectionMultiplexer.Connect(
                           new ConfigurationOptions
                           {
                               EndPoints = { { "myRedis.redns.redis-cloud.com", 18070 } },
                               User = "default",
                               Password = "MyPassword"
                           }
                       );
    
                       var db = muxer.GetDatabase(0); // or your actual DB ID
    
                       // Write a test key
                       db.StringSet("test:key", "Hello from .NET");
    
                       // Read it back
                       string value = db.StringGet("test:key");
    

    – NOT WORKING - BUT I NEED THIS

                        var redisOptions = new ConfigurationOptions
                       {
                           EndPoints = { "myRedis.redns.redis-cloud.com:18071" },
                           User = "default",
                           Password = "MyPassword",
                           AbortOnConnectFail = false,
                           ConnectTimeout = 10000,
                           SyncTimeout = 10000,
                           AsyncTimeout = 10000,
                           DefaultDatabase = 0,
                           Ssl = true,
                           SslHost = "myRedis.redns.redis-cloud.com"
                       };
    
    
    
                        services.AddSignalR().AddStackExchangeRedis(options =>
                       {
                           options.Configuration = redisOptions;
                       });
    
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi @pliaspzero

    Can you configure your Redis and SignalR settings as specified in the sample code below and check if this works in your scenario?

    var redisOptions = new ConfigurationOptions
    {
        EndPoints = { "myRedis.redns.redis-cloud.com:18071" },
        User = "default",
        Password = "MyPassword",
        AbortOnConnectFail = false,
        ConnectTimeout = 10000,
        SyncTimeout = 10000,
        AsyncTimeout = 10000,
        DefaultDatabase = 0,
        Ssl = true,
        SslHost = "myRedis.redns.redis-cloud.com"
    };
    
    services.AddSignalR().AddStackExchangeRedis(redisOptions.EndPoints.ToString(), options =>
    {
        options.ConnectionFactory = async writer =>
        {
            return await ConnectionMultiplexer.ConnectAsync(redisOptions, writer);
        };
    });
    
  • User Avatar
    0
    pliaspzero created

    Hi,

    thanks - still no luck - same error - Chat proposed me this - what do you think?

    1. CustomAbpRedisCacheDatabaseProvider.cs

    public class CustomAbpRedisCacheDatabaseProvider : IAbpRedisCacheDatabaseProvider
    {
    private readonly Lazy<IDatabase> _database;

    public CustomAbpRedisCacheDatabaseProvider(IConfiguration configuration)
    {
        _database = new Lazy<IDatabase>(() =>
        {
            var redisConnectionString = configuration["Abp:RedisCache:ConnectionString"];
            var redisPassword = configuration["Abp:RedisCache:RedisPassword"];
            var redisDatabaseId = configuration.GetValue<int>("Abp:RedisCache:DatabaseId");
    
            var options = new ConfigurationOptions
            {
                EndPoints = { redisConnectionString },
                Password = redisPassword,
                DefaultDatabase = redisDatabaseId,
                Ssl = true,
                AbortOnConnectFail = false,
                ClientName = "AbpRedisCache"
            };
    
            // Zertifikat laden
            var baseCertPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Certificates");
            var caCertPath = Path.Combine(baseCertPath, configuration["Abp:RedisCache:CaCertificateFile"]);
            var caCertificates = new X509Certificate2Collection();
            caCertificates.ImportFromPemFile(caCertPath);
    
            options.CertificateValidation += (sender, cert, chain, errors) =>
            {
                var cust_chain = new X509Chain();
                cust_chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                cust_chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                cust_chain.ChainPolicy.CustomTrustStore.AddRange(caCertificates);
                return cust_chain.Build(new X509Certificate2(cert));
            };
    
            var muxer = ConnectionMultiplexer.Connect(options);
            return muxer.GetDatabase();
        });
    }
    
    public IDatabase GetDatabase()
    {
        return _database.Value;
    }
    

    }

    1. Registrierung in deinem Startup-Modul

    context.Services.Replace(
    ServiceDescriptor.Singleton<IAbpRedisCacheDatabaseProvider, CustomAbpRedisCacheDatabaseProvider>()
    );