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

}

Markdown is supported
Copy & paste or drag & drop images (max 30 MB per image)

6 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
      }
    }
    
    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • User Avatar
    0
    pliaspzero created

    Hi,

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

                        var redisOptions = new ConfigurationOptions
                        {
                            EndPoints = { "&lt;myServer&gt;redns.redis-cloud.com:18070" },
                            User = "default",
                            Password = "mYPassword",
                            Ssl = true,
                            SslHost = "&lt;myServer&gt;redns.redis-cloud.com",
                            AbortOnConnectFail = false
                        };
    
    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • 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;
                       });
    
    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • 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);
        };
    });
    
    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • 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&lt;IDatabase&gt;(() =>
        {
            var redisConnectionString = configuration["Abp:RedisCache:ConnectionString"];
            var redisPassword = configuration["Abp:RedisCache:RedisPassword"];
            var redisDatabaseId = configuration.GetValue&lt;int&gt;("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>() );

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi @pliaspzero

    You can make certificate settings in Startup. It would be more correct to make Redis database settings in the PreInitialize method of the WebCore module class. However, the method you specified can also be used in Startup. Make sure that the connection string and certificate paths are correct here.

    Test Redis with ping:

    var isConnected = muxer.GetDatabase().Ping();
    

    Define the ConfigureRedis Method: You can configure the Redis connection settings in the ConfigureRedis() method, including the certificate selection.

    You can add the following to your Startup.cs file.

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        //...
    
        services.AddSingleton< IConnectionMultiplexer >(provider => ConfigureRedis());
    }
    
    private IConnectionMultiplexer ConfigureRedis()
    {
        var redisConfig = new ConfigurationOptions
        {
            Ssl = true,
            AllowAdmin = true,
            AbortOnConnectFail = false,
        };
    
        // Load certificates for secure connection
        redisConfig.CertificateSelection += (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) =>
        {
            // Load the client certificate (redis-client-cert.pem) and client private key (redis-client-key.pem)
            var cert = new X509Certificate2("path/to/redis-client-cert.pfx", "your-cert-password", X509KeyStorageFlags.MachineKeySet);
    
            // Load the CA certificate (ca-cert.pem)
            var caCert = new X509Certificate2("path/to/ca-cert.pem");
    
            // Add the CA certificate to the certificate collection
            localCertificates.Add(caCert);
    
            return cert;
        };
    
        return ConnectionMultiplexer.Connect(redisConfig);
    }
    
    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)