Skip to content

[RedisCache] Allow injection of ConnectionMultiplexer #28379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
epignosisx opened this issue Jul 16, 2017 · 72 comments
Closed

[RedisCache] Allow injection of ConnectionMultiplexer #28379

epignosisx opened this issue Jul 16, 2017 · 72 comments
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares feature-caching Includes: StackExchangeRedis and SqlServer distributed caches help wanted Up for grabs. We would accept a PR to help resolve this issue
Milestone

Comments

@epignosisx
Copy link
Contributor

epignosisx commented Jul 16, 2017

Currently the RedisCache creates the ConnectionMultiplexer and does not allow access to it. This prevents the application from reusing the ConnectionMultiplexer for other operations like PubSub.

Granted, the application could create another ConnectionMultiplexer, but the recommendation is to have only one per application.

I was thinking if it would be possible to have a separate constructor for RedisCache that would receive the ConnectionMultiplexer. I understand that so far Microsoft.Extensions.Caching.Redis did not leak any implementation details about its usage of StackExchange.Redis. However, I noticed that Microsoft.AspNetCore.DataProtection.Redis leaks StackExchange.Redis types here. So, I'm unclear about how you feel about keeping StackExchange.Redis as an implementation detail.

Proposed API

namespace Microsoft.Extensions.Caching.StackExchangeRedis
{
    public class RedisCacheOptions
    {
+       Func<IConnectionMultiplexer> ConnectionMultiplexerFactory { get; set; }     
    }
@epignosisx
Copy link
Contributor Author

epignosisx commented Jul 16, 2017

Just noticed there is an open pull request enabling this scenario. But did not find an issue discussing the proposed change, so I'll keep this one open for discussion.

@muratg
Copy link
Contributor

muratg commented Aug 15, 2017

Closing aspnet/Caching#318 as it entails to a breaking change. Keeping this bug open for any alternatives or for a potential major release change.

@israellot
Copy link

I believe that injecting IConnectionMultiplexer as a dependency is the path to go.

I share @epignosisx concerns about StackExchange.Redis being a implementation detail or not.
The pragmatic way to address it would be have a service interface and ** StackExchange.Redis ** being used in a adaptor that implements that interface. But that would possibly mean too many levels of generalization though.

@epignosisx
Copy link
Contributor Author

@muratg could you share where does the team stand on whether StackExchange.Redis should be kept as an implementation detail or not?

Answering this question is critical to make progress on this issue. As @israellot pointed out the alternatives are more abstractions that probably will be undesirable.

@muratg
Copy link
Contributor

muratg commented Aug 16, 2017

cc @davidfowl

@davidfowl
Copy link
Member

Yes we should allow for this. Not sure about injecting the IConnectionMultiplexer as a dependency though we should have an option that allows setting a Func<IConnectionMultiplexer>

@JadynWong
Copy link

I know the application has created a ConnectionMultiplexer.
Why the application must be create another ConnectionMultiplexer?

@muratg
Copy link
Contributor

muratg commented Jan 3, 2018

Tentatively putting this in 2.2.0. We'll need to identify what the best way to achieve this is.

@schneiBoy
Copy link

@shravan2x
Copy link

@muratg Any update to this?

@ekarlso
Copy link

ekarlso commented Aug 5, 2018

Over a year old and nothing new ?

@muratg
Copy link
Contributor

muratg commented Aug 31, 2018

This is not currently in scope. We'll revisit in a future release.

cc @DamianEdwards @davidfowl

@effyteva
Copy link

effyteva commented Oct 3, 2018

+1
Perhaps a much easier solution would be to allow getting the current Multiplexer? This way Microsoft.Extensions.Caching.Redis would create the Multiplexer instance, and any external code would use the same instance.

@shravan2x
Copy link

shravan2x commented Oct 3, 2018

@effyteva That would become a problem if other libraries did the same thing - everyone would create their own multiplexer and expect others to use theirs.

@aspnet-hello aspnet-hello transferred this issue from aspnet/Caching Dec 13, 2018
@kinosang
Copy link

For Data Protection we can use .PersistKeysToStackExchangeRedis(ConnectionMultiplexer, RedisKey), how about adding an overloading such as AddStackExchangeRedisCache(ConnectionMultiplexer, InstanceName)?

@hjkl950217
Copy link

I also encountered the same problem, for some redis data, I need to use IConnectionMultiplexer

@bidianqing
Copy link

@muratg Any update to this?

@muratg
Copy link
Contributor

muratg commented Jun 27, 2019

I'm no longer working with the ASP.NET team, so paging @anurse. @DamianEdwards and @davidfowl here.

@analogrelay
Copy link
Contributor

We don't have plans to do this for 3.0, but it is on our backlog to consider for a future version.

@bidianqing
Copy link

bidianqing commented Aug 8, 2019

@bragma
Copy link

bragma commented Sep 30, 2019

Hi, it seems this feature won't be available anytime soon. Is there a workaround for this? I need to use the redis cache both via IDistributedCache and with native redis commands. Thanks!

@analogrelay
Copy link
Contributor

@bragma I don't disagree at all that it would be a good plan for us to allow injecting it, but can you provide more context on why you require that your native redis commands and the redis cache use the same instance of the ConnectionMultiplexer?

@kinosang
Copy link

kinosang commented Sep 30, 2019

@anurse I think that's why there's ConnectionMultiplexer.

Because the ConnectionMultiplexer does a lot, it is designed to be shared and reused between callers. You should not create a ConnectionMultiplexer per operation. It is fully thread-safe and ready for this usage.

@epignosisx
Copy link
Contributor Author

@bragma the workaround I have ended up using is getting a hold of the ConnectionMultiplexer via private reflection 😨 . This is basically what I have:

    public static class RedisCacheExtensions
    {
        /// <summary>
        /// The ASP.NET Core's RedisCache does not expose the ConnectionMultiplexer required for more advanced Redis scenarios
        /// and it is recommended to have just one ConnectionMultiplexer.
        /// We are left with no option but to use reflection to get a hold of the connection.
        /// </summary>
        public static async Task<ConnectionMultiplexer> GetConnectionAsync(this RedisCache cache, CancellationToken cancellationToken = default)
        {
            //ensure connection is established
            await((Task)typeof(RedisCache).InvokeMember("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, cache, new object[] { cancellationToken }));

            //get connection multiplexer
            var fi = typeof(RedisCache).GetField("_connection", BindingFlags.Instance | BindingFlags.NonPublic);
            var connection = (ConnectionMultiplexer)fi.GetValue(cache);
            return connection;
        }
    }

Usage:

    public class RedisHealthCheck : IHealthCheck
    {
        private readonly RedisCache _cache;

        public RedisHealthCheck(IDistributedCache cache)
        {
            _cache = (RedisCache)cache;
        }

        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            try
            {
                var connection = await _cache.GetConnectionAsync(cancellationToken);
                await connection.GetDatabase()
                    .PingAsync();

                return HealthCheckResult.Healthy();
            }
            catch (Exception ex)
            {
                return new HealthCheckResult(context.Registration.FailureStatus, exception: ex);
            }
        }
    }

@analogrelay
Copy link
Contributor

I think that's why there's ConnectionMultiplexer.

Yep, I understand the purpose. Just trying to understand what issues you're seeing with using two separate instances to help prioritize. Is there extra load in your system? (memory usage, cpu, etc.)

We'll definitely be investigating this, but I just want to understand how this issue is negatively impacting applications right now in order to help us prioritize.

@bragma
Copy link

bragma commented Sep 30, 2019

@bragma I don't disagree at all that it would be a good plan for us to allow injecting it, but can you provide more context on why you require that your native redis commands and the redis cache use the same instance of the ConnectionMultiplexer?

Well, the context is more or less the same as others requesting for it. I need to stuff some data in the cache preserving compatibility with other services reading it via IDistributedCache and also use all other nice Redis data types for other purposes. Of course I could just ditch the IDistributedCache implementation and use only the Redis package replicating the data format, but any change in the implementation of distributed cache for redis would require me to change my code.
About using 2 different instances, I have no proof it will cause problems, yet it seems a waste of resources to me. I am no expert but 2 different contexts each ignorant of the other will at lease cause double connections/threads.
I also agree it is not a priority, that's why I am asking for a kind of workaround instead of an implementation. I am not expert enough to make up mine, so I am asking:

  1. Is it possible and what's the correct way to create a singleton ConnectionMultiplexer and use it in both RedisCache and directly?
  2. If not, what's the bast way to create a privatec ConnectionMultiplexer and a RedisCache without having both interfere?

Thanks a lot!

@analogrelay
Copy link
Contributor

About using 2 different instances, I have no proof it will cause problems, yet it seems a waste of resources to me. I am no expert but 2 different contexts each ignorant of the other will at lease cause double connections/threads.

This isn't necessarily wrong, but it would probably be best to have actual data to know the impact of that.

I don't have a specific workaround for you unfortunately. The main issue here is that the cache doesn't give you a way to specify the multiplexer, which isn't easy to work around. Honestly, I would investigate the impact of two separate ConnectionMultiplexer instances is on your actual app and see if that's a sufficient workaround here.

@bragma
Copy link

bragma commented Oct 4, 2019

I was wondering an idea, which may be totally naive and stupid, so I am exposing it for comments. Suppose RedisCache is changed by adding something like:

        public IDatabase GetRedisDatabase()
        {
            Connect();
            return _cache;
        }

        public async Task<IDatabase> GetRedisDatabaseAsync()
        {
            await ConnectAsync();
            return _cache;
        }

And an interface like this is added:

    public interface IDistributedRedisCache : IDistributedCache
    {
        IDatabase GetRedisDatabase();
        Task<IDatabase> GetRedisDatabaseAsync();
    }

This would allow to create RedisCache as a singleton for a IDistributedRedisCache and IDistributedCache would still work, I suppose.

It seems like a minor change to me. I am missing anything?

Thanks a lot!

@epignosisx
Copy link
Contributor Author

epignosisx commented Oct 4, 2019

I like the idea of RedisCache exposing public methods to get the inner workings, however:

  • For ultimate flexibility expose the ConnectionMultiplexer instead of IDatabase.

  • The additional interface IDistributedRedisCache is leaking StackExchange.Redis types, so it is not much of an abstraction. Something like ServiceStack.Redis would not be able to implement it. Probably better to leave this interface as an application concern to facilitate unit testing instead of having it in the framework.

  • Still I think constructor injection is needed. I can imagine use cases needing to connect to Redis during startup and then just passing the initialized ConnectionMultiplexer to RedisCache during DI configuration.

@davidfowl
Copy link
Member

@napalm684 I've updated the original issue with an API proposal. We'll review the API, you can prepare a PR in the meantime or wait for final approval.

@davidfowl davidfowl added the api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews label Mar 28, 2021
@NapalmCodes
Copy link
Contributor

NapalmCodes commented Mar 29, 2021

@davidfowl I guess I'm a little confused what the factory provides when the property is already private on that class if I recall correctly. Is there an example of this I could reference?

@davidfowl
Copy link
Member

@davidfowl I guess I'm a little confused what the factory provides when the property is already private on that class if I recall correctly. Is there an example of this I could reference?

The options could have a ConnectionMultiplexer instance instead of a Func<ConnectionMultiplexer> (if that's what people need). I assume the use case for this is to potentially reduce the number of connections to redis in the application, so we'd also need a way to know if we should be making a connection or not.

@jjxtra
Copy link

jjxtra commented Mar 29, 2021

Putting the burden on the application to create and pass a connection multiplexer in the options would add un-needed complexity. Now the client has to know how to create this type properly and connect, which can be error prone if not done right.

Being able to inject IConnectionMultiplexer into a constructor automatically should be all that is needed, since the Microsoft frameworks already have this created under the hood. The public interface of the nuget packages need not change, since they already reference the stack exchange nuget.

@davidfowl
Copy link
Member

They can inject the multiplexer trivially if that's what they want to do and apply it to the options. We generally don't like assuming the services that are in the container should be automagically consumed by components, especially when they are optional. The API above is the standard pattern introduced in all extensions and ASP.NET Core APIs for configurability.

@NapalmCodes
Copy link
Contributor

Is there a process to take this? I would like to make a PR if someone hasn't already.

@davidfowl
Copy link
Member

We're gonna discuss the API next week and then you can send the PR

@NapalmCodes
Copy link
Contributor

Awesome will you post the result of the discussion here and then I can submit?

@NapalmCodes
Copy link
Contributor

So I took a stab in this PR #31879 . I would like some feedback (whether I did this properly - first timer here) and am willing to assist in whatever way necessary to apply feedback.

@speterson-zoll
Copy link

We would benefit from this fix. Can someone review PR #31879?

@NapalmCodes
Copy link
Contributor

@davidfowl any word on the api design discussion? If things didn't change my PR is still out there. Otherwise let's talk about updates/feedback? Thanks!

@pranavkm pranavkm added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Apr 26, 2021
@pranavkm
Copy link
Contributor

Additional API feedback provided in the PR

@NapalmCodes
Copy link
Contributor

Additional API feedback provided in the PR

I commented in the PR @pranavkm let's talk about a few things please.

@NapalmCodes
Copy link
Contributor

With the merge of PR #32266 can this be closed?

@JunTaoLuo
Copy link
Contributor

Yup, thanks for the contribution @napalm684 !

@RehanSaeed
Copy link
Contributor

RehanSaeed commented May 5, 2021

I've registered IConnectionMultiplexer in IoC. The Hot Chocolate GraphQL server allows you to resolve IConnectionMultiplexer from IoC like so:

servoces.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect("redis:6379"));
builder.AddRedisQueryStorage(x => x.GetApplicationServices().GetRequiredService<IConnectionMultiplexer>().GetDatabase());
builder.AddRedisSubscriptions(x => x.GetRequiredService<IConnectionMultiplexer>());

I've also raised an issue in Xabril health checks to do something similar in Xabaril/AspNetCore.Diagnostics.HealthChecks#750:

services
    .AddHealthChecks()
    .AddRedis(sp => sp.GetRequiredService<IConnectionMultiplexer>());

Can this be a supported scenario for the IDistributedCache also? Setting up Redis in three different ways is not ideal.

@ghost ghost locked as resolved and limited conversation to collaborators Jun 4, 2021
@amcasey amcasey added area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares and removed area-runtime labels Jun 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares feature-caching Includes: StackExchangeRedis and SqlServer distributed caches help wanted Up for grabs. We would accept a PR to help resolve this issue
Projects
None yet
Development

No branches or pull requests