2

We are facing problems with Redis caching and it's causing crashes in our site.

The following is how we implemented it:

We used the following connection string:

"*******.redis.cache.windows.net:6380,password=*****=,ssl=True,abortConnect=False"

We created a service class:

using Microsoft.Extensions.Options;
using SarahahDataAccessLayer;
using StackExchange.Redis;
using System;

namespace Sarahah.Services
{
    public class RedisService
    {
        private static Lazy<ConnectionMultiplexer> lazyConnection;
        private readonly ApplicationSettings _settings;
        public RedisService(IOptions<ApplicationSettings> settings)
        {
            _settings = settings.Value;
            lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
            {
                return ConnectionMultiplexer.Connect(_settings.RedisConnection);
            });
        }



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

Then in Startup.cs I use the following:

services.AddSingleton<RedisService>();

Then in controllers we use dependency injection and we assign to a multiplexer:

connectionMultiplexer = redisService.Connection;

This is how we get from the cache:

 private async Task<string> GetFromCache(string key)
    {
        if (connectionMultiplexer.IsConnected)
        {
            var cache = connectionMultiplexer.GetDatabase();

                return await cache.StringGetAsync(key);
        }
        else
        {
            return null;
        }
    }

This is how we delete:

  private async Task DeleteFromCache(string subdomain)
    {

            if (connectionMultiplexer.IsConnected)
            {
                var cache = connectionMultiplexer.GetDatabase();
                await cache.KeyDeleteAsync(subdomain).ConfigureAwait(false);
            }
    }

This is how we add:

 {
        if (connectionMultiplexer.IsConnected)
        {
            var cache = connectionMultiplexer.GetDatabase();

                TimeSpan expiresIn;
                // Search Cache
                if (key.Contains("-"))
                {
                    expiresIn = new TimeSpan(0, GetMessagesCacheExpiryMinutes, 0);
                }
                // User info cache
                else
                {
                    expiresIn = new TimeSpan(GetProfileCacheExpiryHours, 0, 0);
                }
                await cache.StringSetAsync(key, serializedData, expiresIn).ConfigureAwait(false);

        }

However, we get the following error: No connection is available to service this operation

Although we have a lot of users, we only see few connections in Azure portal:

Very small number of connections

Please note that we hosted the redis cache in the same region of the web app.

Your support is appreciated.

1 Answer 1

2

Each time your dependency injection calls instantiates the RedisService class, your code ends up assigning a new Lazy<ConnectionMultiplexer> to lazyConnection, thus resulting in a new connection as well as a connection leak as you are not calling Close() or Dispose() on the old lazyConnection.

Try changing your code like this:

In Startup.cs:

public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            .........<whatever you have here>
            services.AddSingleton<RedisService>();
            services.Configure<ApplicationSettings>(options => Configuration.GetSection("ApplicationSettings").Bind(options));
        }

RedisService.cs

public class RedisService
{
    private readonly ApplicationSettings _settings;
    private static Lazy<ConnectionMultiplexer> lazyConnection;
    static object connectLock = new object();

    public RedisService(IOptions<ApplicationSettings> settings)
    {
        _settings = settings.Value;
        if (lazyConnection == null)
        {
            lock (connectLock)
            {
                if (lazyConnection == null)
                {
                    lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
                    {
                        return ConnectionMultiplexer.Connect(_settings.RedisConnection);
                    });
                }
            }
        }
    }

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

ApplicationSettings.cs

public class ApplicationSettings
    {
        public string RedisConnection { get; set; }
    }

appsettings.json

{
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    },
    "ApplicationSettings": {
        "RedisConnection": "yourcachename.redis.cache.windows.net:6380,password=yourpassword,ssl=True,abortConnect=False,syncTimeout=4000"
    }
}

HomeController.cs

public class HomeController : Controller
    {
        private RedisService redisService;
        private ConnectionMultiplexer connectionMultiplexer;
        public HomeController(IOptions<ApplicationSettings> settings)
        {
            redisService = new RedisService(settings);
            connectionMultiplexer = RedisService.Connection;
        }
        public IActionResult Index()
        {
            AddToCache("foo1", "bar").GetAwaiter().GetResult();

            return View();
        }

        private async Task<string> GetFromCache(string key)
        {
            if (connectionMultiplexer.IsConnected)
            {
                var cache = connectionMultiplexer.GetDatabase();

                return await cache.StringGetAsync(key);
            }
            else
            {
                return null;
            }
        }

        private async Task DeleteFromCache(string subdomain)
        {
            if (connectionMultiplexer.IsConnected)
            {
                var cache = connectionMultiplexer.GetDatabase();
                await cache.KeyDeleteAsync(subdomain).ConfigureAwait(false);
            }
        }

        private async Task AddToCache(string key, string serializedData)
        {
            var GetMessagesCacheExpiryMinutes = 5;
            var GetProfileCacheExpiryHours = 1;
            if (connectionMultiplexer.IsConnected)
            {
                var cache = connectionMultiplexer.GetDatabase();

                TimeSpan expiresIn;
                // Search Cache
                if (key.Contains("-"))
                {
                    expiresIn = new TimeSpan(0, GetMessagesCacheExpiryMinutes, 0);
                }
                // User info cache
                else
                {
                    expiresIn = new TimeSpan(GetProfileCacheExpiryHours, 0, 0);
                }
                await cache.StringSetAsync(key, serializedData, expiresIn).ConfigureAwait(false);

            }
        }
Sign up to request clarification or add additional context in comments.

14 Comments

Hi, thanks a lot. But I notice you mention: redisService = new RedisService(settings); What is redisService variable? We have modified the implementation to the following: new RedisService(settings); connectionMultiplexer = RedisService.Connection; is this what you meant by any chance?
I tried to create a new variable (redisService) but then I can't call redisService.connection as I get an error.
When we used the way I mentioned in the first column the error still shows. Thanks.
I have updated the code above to have all the pieces you need. I hope that helps.
Unfortunately I get: Member RedisService.Connection cannot be accessed with an instance reference, qualify it with a type name instead. (on this line: connectionMultiplexer = redisService.Connection;)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.