Exasol Ado.Net 提供商的自定义连接池

Custom Connection pool for Exasol Ado.Net provider

我们正在使用内存数据库 Exasol,它确实提供了 Ado.Net 提供程序,但它似乎缺少一些重要的功能,例如 ConnectionPooling,因此每个连接都会创建并根据请求销毁,这会影响我们的性能,因为我们正在连接到 AWS 上的托管数据库。我创建了一个简单的 ConnectionPool,具有 Resize 的能力,请建议这是否可以达到目的,或者我需要做更多的事情。

请注意,我不是在寻找代码审查,而是对我在当前实施中可能遗漏的内容进行批判性分析,如果有可用的实施(Nuget,Git),我可以重复使用。目前我正在根据大小调整大小,如何根据时间实现相同的大小,在空闲的一定持续时间内,应从队列中清除少量资源,从而减小大小。

重要细节:

  1. 在内部使用 ConcurrentQueue 从多个客户端安全地访问资源
  2. 使用AutoResetEvent等待,如果池为空则发出信号
  3. 使用 TPL 进行大小调整操作,无需停止调用代码,我的理解是即使在客户端调用 returns 时也能正常工作,因为它在 Threadpool thread

    class ExasolConnectionPool
    {
        /// <summary>
        /// Thread safe queue for storing the connection objects
        /// </summary>
        private ConcurrentQueue<EXAConnection> ExasolConnectionQueue { get; set; }
    
        /// <summary>
        /// Number of connections on the Connection pool
        /// </summary>
        private int _connectionCount;
    
        /// <summary>
        /// Max Pool Size
        /// </summary>
        private int MaxPoolSize { get; set; }
    
        /// <summary>
        /// Min Pool Size
        /// </summary>
        private int MinPoolSize { get; set; }
    
        /// <summary>
        /// Increase in Pool Size
        /// </summary>
        private int IncreasePoolSize { get; set; }
    
        /// <summary>
        /// Decrease in Pool Size
        /// </summary>
        private int DecreasePoolSize { get; set; }
    
        /// <summary>
        /// Connection string for the Connection pool connections
        /// </summary>
        private string ConnectionString { get; set; }
    
        /// <summary>
        /// Auto Reset event for the connection pool
        /// </summary>
        private AutoResetEvent ExasolConnectionPoolAre { get; set; }
    
        /// <summary>
        /// Connection pool specific Lock object
        /// </summary>
        private readonly object lockObject;
    
        /// <summary>
        /// Connection pool constructor
        /// </summary>
        /// <param name="connectionString"></param>
        /// <param name="poolSize"></param>
        public ExasolConnectionPool(string connectionString, int poolSize = 10)
        {
            // Set the Connection String
            ConnectionString = connectionString;
    
            // Intialize the Connection Queue
            ExasolConnectionQueue = new ConcurrentQueue<EXAConnection>();
    
            // Enqueue initial set of connections
            for (int counter = 0; counter < poolSize; counter++)
            {
                var exaConnection = new EXAConnection {ConnectionString = ConnectionString};
    
                ExasolConnectionQueue.Enqueue(exaConnection);
            }
    
            // Initialize Lock object 
            lockObject = new object();
    
            // Set the Connection queue count
            _connectionCount = poolSize;
    
            // Max pool size
            MaxPoolSize = poolSize;
    
            // Min Pool Size
            MinPoolSize = 2;
    
            IncreasePoolSize = 5;
    
            DecreasePoolSize = 3;
    
            ExasolConnectionPoolAre = new AutoResetEvent(false);
        }
    
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public EXAConnection GetConnection()
        {
            // Return ExaConnection object
            EXAConnection returnConnection;
    
            // Try Dequeue the connection object from the Concurrent Queue
            var validExasolConnection = ExasolConnectionQueue.TryDequeue(out returnConnection);
    
            // If No Valid connection is available, then wait using AutoReset signaling mechanism
            while (!validExasolConnection)
            {
                ExasolConnectionPoolAre.WaitOne();
    
                validExasolConnection = ExasolConnectionQueue.TryDequeue(out returnConnection);
            }
    
            // Thread safe connection count update
            Interlocked.Decrement(ref _connectionCount);
    
            Task.Factory.StartNew(() =>
            {
                lock (lockObject)
                {
                    if (_connectionCount > MinPoolSize) return;
    
                    for (var counter = 0; counter < IncreasePoolSize; counter++)
                    {
                        var exaConnection = new EXAConnection {ConnectionString = ConnectionString};
    
                        ExasolConnectionQueue.Enqueue(exaConnection);
    
                        Interlocked.Increment(ref _connectionCount);
                    }
                }
            });
    
            return (returnConnection);
        }
    
        /// <summary>
        /// 
        /// </summary>
        /// <param name="returnedConnection"></param>
        public void ReturnConnection(EXAConnection returnedConnection)
        {
            ExasolConnectionQueue.Enqueue(returnedConnection);
    
            Interlocked.Increment(ref _connectionCount);
    
            ExasolConnectionPoolAre.Set();
    
            Task.Factory.StartNew(() =>
            {
                lock (lockObject)
                {
                    if (_connectionCount < MaxPoolSize * 1.5) return;
    
                    for (var counter = 0; counter < DecreasePoolSize; counter++)
                    {
                        EXAConnection exaConnection;
    
                        if (ExasolConnectionQueue.TryDequeue(out exaConnection))
                        {
                            exaConnection.Dispose();
    
                            exaConnection = null;
    
                            Interlocked.Decrement(ref _connectionCount);
                        }
                    }
                }
            });
        }
    }
    

您的池的实现很好。我不知道有任何 NuGet 实现这么小,而且对您的情况来说并不过分复杂。我只是想添加少量建议,您可以自行研究。

  1. StartNew is Dangerous Stephen Cleary 的文章非常好 post 关于您用于调整逻辑大小的方法。最重要的部分是:

    Thread "A" will run on whatever TaskScheduler is currently executing!

    因此您的代码有时 可以使用UI 线程上下文并降低应用程序的性能。如果它适合您(例如,对于 ASP.NET 应用程序),那很好,但如果不适合,我建议您改用 Task.Run 方法。您还可以查看 Stephen 关于 TPL 最佳实践的博客。

  2. 一般来说,调整大小的逻辑是通过简单的方式完成的,将大小加倍,所以如果你达到了限制,大小就会变成它的两倍,反之亦然。我认为为用户提供管理此常量的能力可能会导致一些奇怪的错误,例如负池大小和类似错误。

    所以你应该像 private 那样做你的 属性 设置器,并且对于我来说,删除有关调整大小的属性。也许将来您可以为您的应用程序平均收集池大小的统计信息,并将该参数用作默认值。