【问题标题】:Azure Redis Cache - pool of ConnectionMultiplexer objectsAzure Redis 缓存 - ConnectionMultiplexer 对象池
【发布时间】:2015-06-18 16:17:27
【问题描述】:

我们在我们的应用程序中使用 C1 Azure Redis 缓存。最近,我们在 GET 操作上遇到了很多超时。

According to this article,一种可能的解决方案是实现 ConnectionMultiplexer 对象池。

另一种可能的解决方案是使用 ConnectionMultiplexer 池 客户端中的对象,然后选择“加载最少的” ConnectionMultiplexer 发送新请求时。这应该防止 一次超时导致其他请求也超时。

如何使用 C# 实现 ConnectionMultiplexer 对象池?

编辑:

Related question that I asked recently.

【问题讨论】:

  • 您是否正在执行任何特别长时间运行的操作?在跳入游泳池之前,我很想了解这是否是延迟、带宽饱和、服务器拥塞等...
  • @MarcGravell - 我们几乎解决了所有的超时问题,只需重新编写一些可以提高性能的代码。这并不是真正需要的,但我仍然有兴趣查看一段实现 ConnectionMultiplexers 池的代码。
  • @JakubHolovsky 你能分享一些关于如何重写以获得更好性能的经验吗?
  • @huangcd - 是的,当然,看看我的答案stackoverflow.com/questions/29569997/…
  • @JakubHolovsky 谢谢!

标签: c# caching azure stackexchange.redis azure-redis-cache


【解决方案1】:

如果您使用的是 StackExchange.Redis,根据此github issue,您可以在连接多路复用器对象上使用 TotalOutstanding 属性。

这是我想出的一个实现,它工作正常:

public static int POOL_SIZE = 100;
private static readonly Object lockPookRoundRobin = new Object();
private static Lazy<Context>[] lazyConnection = null;

//Static initializer to be executed once on the first call
private static void InitConnectionPool()
{
    lock (lockPookRoundRobin)
    {
        if (lazyConnection == null) {
             lazyConnection = new Lazy<Context>[POOL_SIZE];
        }


        for (int i = 0; i < POOL_SIZE; i++){
            if (lazyConnection[i] == null)
                lazyConnection[i] = new Lazy<Context>(() => new Context("YOUR_CONNECTION_STRING", new CachingFramework.Redis.Serializers.JsonSerializer()));
        }
    }
}

private static Context GetLeastLoadedConnection()
{
    //choose the least loaded connection from the pool
    /*
    var minValue = lazyConnection.Min((lazyCtx) => lazyCtx.Value.GetConnectionMultiplexer().GetCounters().TotalOutstanding);
    var lazyContext = lazyConnection.Where((lazyCtx) => lazyCtx.Value.GetConnectionMultiplexer().GetCounters().TotalOutstanding == minValue).First();
    */

    // UPDATE following @Luke Foust comment below
    Lazy<Connection> lazyContext;

    var loadedLazys = lazyConnection.Where((lazy) => lazy.IsValueCreated);
    if(loadedLazys.Count()==lazyConnection.Count()){
        var minValue = loadedLazys.Min((lazy) => lazy.Value.TotalOutstanding);
        lazyContext = loadedLazys.Where((lazy) => lazy.Value.TotalOutstanding == minValue).First();
    }else{
        lazyContext = lazyConnection[loadedLazys.Count()];
    }
    return lazyContext.Value;
}

private static Context Connection
{
    get
    {
        lock (lockPookRoundRobin)
        {
            return GetLeastLoadedConnection();
        }
    }
}

public RedisCacheService()
{
    InitConnectionPool();
}

【讨论】:

  • 对lazyConnection.Min 的调用不会导致整个池连接/初始化从而使Lazy 的使用无效
  • 您说得对,我更新了答案以解决此问题并正确使用连接池。您可以在线测试此代码:here
  • @Gomino,这里的上下文是什么。我可以看到它来自一个包 Microsoft.AspNetCore.Hosting.Internal.HostingApplication。它的构造函数中没有参数。您指的是哪个包获取 Context 对象。同样在这一行 Lazy lazyContext;从哪里获得连接类?
  • @AnilPurswani 你现在应该使用RedisContext 类。事实上,Context 类来自 CachingFramework.Redis,但在 2018 年 5 月被提交 a0a83e0 标记为过时,并已被提交 023a4b3 删除
  • @Gomino,我使用的是 StackExchange.Redis,所以我使用了 ConnectionMultiplexer,它运行良好。而且就像您提供的链接一样,我使用了“GetCounters().TotalOutstanding”
【解决方案2】:

您还可以使用StackExchange.Redis.Extensions 以更简单的方式完成此操作

示例代码:

    using StackExchange.Redis;
    using StackExchange.Redis.Extensions.Core.Abstractions;
    using StackExchange.Redis.Extensions.Core.Configuration;
    using System;
    using System.Collections.Concurrent;
    using System.Linq;

    namespace Pool.Redis
    {
    /// <summary>
    /// Provides redis pool
    /// </summary>
    public class RedisConnectionPool : IRedisCacheConnectionPoolManager
    {
        private static ConcurrentBag<Lazy<ConnectionMultiplexer>> connections;
        private readonly RedisConfiguration redisConfiguration;

        public RedisConnectionPool(RedisConfiguration redisConfiguration)
        {
            this.redisConfiguration = redisConfiguration;
            Initialize();
        }

        public IConnectionMultiplexer GetConnection()
        {
            Lazy<ConnectionMultiplexer> response;
            var loadedLazys = connections.Where(lazy => lazy.IsValueCreated);

            if (loadedLazys.Count() == connections.Count)
            {
                response = connections.OrderBy(x => x.Value.GetCounters().TotalOutstanding).First();
            }
            else
            {
                response = connections.First(lazy => !lazy.IsValueCreated);
            }

            return response.Value;
        }

        private void Initialize()
        {
            connections = new ConcurrentBag<Lazy<ConnectionMultiplexer>>();

            for (int i = 0; i < redisConfiguration.PoolSize; i++)
            {
                connections.Add(new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(redisConfiguration.ConfigurationOptions)));
            }
        }

        public void Dispose()
        {
            var activeConnections = connections.Where(lazy => lazy.IsValueCreated).ToList();
            activeConnections.ForEach(connection => connection.Value.Dispose());
            Initialize();
        }
    }
}

RedisConfiguration 是这样的:

            return new RedisConfiguration()
        {
            AbortOnConnectFail = true,
            Hosts = new RedisHost[] {
                                      new RedisHost() 
                                      {
                                          Host = ConfigurationManager.AppSettings["RedisCacheAddress"].ToString(),
                                          Port = 6380
                                      },
                                    },
            ConnectTimeout = Convert.ToInt32(ConfigurationManager.AppSettings["RedisTimeout"].ToString()),
            Database = 0,
            Ssl = true,
            Password = ConfigurationManager.AppSettings["RedisCachePassword"].ToString(),
            ServerEnumerationStrategy = new ServerEnumerationStrategy()
            {
                Mode = ServerEnumerationStrategy.ModeOptions.All,
                TargetRole = ServerEnumerationStrategy.TargetRoleOptions.Any,
                UnreachableServerAction = ServerEnumerationStrategy.UnreachableServerActionOptions.Throw
            },
            PoolSize = 50
        };

【讨论】:

  • 你在 dispose 方法中调用Initialize,真的应该在那里吗?
猜你喜欢
  • 1970-01-01
  • 2013-09-02
  • 2016-09-10
  • 2019-05-17
  • 2016-03-29
  • 1970-01-01
  • 2016-06-04
  • 1970-01-01
  • 2021-01-24
相关资源
最近更新 更多