C# 具有多线程服务的数据库连接池

C# 具有多线程服务的数据库连接池,c#,multithreading,service,connection-pooling,C#,Multithreading,Service,Connection Pooling,我有一个.NET4C服务,它使用TPL库进行线程处理。我们最近将其切换为也使用连接池,因为一个连接正在成为处理的瓶颈 以前,我们使用lock子句来控制连接对象上的线程安全。由于工作将备份,队列将作为任务存在,许多线程(任务)将等待lock子句。现在,在大多数情况下,线程等待数据库IO,并且工作进程要快得多 然而,现在我正在使用连接池,我们有一个新问题。一旦达到最大连接数(默认值为100),如果请求进一步连接,则会出现超时(请参阅)。发生这种情况时,将抛出一个异常,说明“连接请求超时” 我的所有I

我有一个.NET4C服务,它使用TPL库进行线程处理。我们最近将其切换为也使用连接池,因为一个连接正在成为处理的瓶颈

以前,我们使用lock子句来控制连接对象上的线程安全。由于工作将备份,队列将作为任务存在,许多线程(任务)将等待lock子句。现在,在大多数情况下,线程等待数据库IO,并且工作进程要快得多

然而,现在我正在使用连接池,我们有一个新问题。一旦达到最大连接数(默认值为100),如果请求进一步连接,则会出现超时(请参阅)。发生这种情况时,将抛出一个异常,说明“连接请求超时”

我的所有IDisPobles都在using语句中,我正在正确管理我的连接。发生这种情况的原因是请求的工作超过了池可以处理的工作(这是预期的)。我理解抛出此异常的原因,并且知道处理它的方法。一次简单的重试感觉像是一次黑客攻击。我还意识到我可以通过连接字符串来增加超时时间,但是这并不是一个解决方案。在以前的设计中(没有池),由于应用程序中的锁,工作项将被处理


处理此场景的好方法是什么,以确保所有工作都得到处理?

您可以使用
Parallel.ForEach()
方法控制并行度,如下所示:

var items = ; // your collection of work items
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 100 };
Parallel.ForEach(items, parallelOptions, ProcessItem)
在本例中,我选择将度设置为100,但您可以选择一个对当前连接池实现有意义的值


当然,此解决方案假定您预先有一组工作项。但是,如果您通过一些外部机制(如传入的web请求)创建新任务,那么异常实际上是一件好事。在这一点上,我建议您使用并发队列数据结构,在这里您可以放置工作项,并在工作线程可用时将其弹出。

最简单的解决方案是将连接超时增加到您愿意在返回失败之前阻止请求的时间长度。一定有“太长”的时间


这有效地将连接池用作超时的工作队列。这比自己尝试实现一个要容易得多。您必须检查连接池是否公平(FIFO)。

另一种方法是在从池中检索连接(并希望返回连接)的代码中使用循环。sempahore类似于lock语句,只是它允许一次可配置的请求者数量,而不仅仅是一个

这样做应该可以:

//Assuming mySemaphore is a semaphore instance, e.g. 
// public static Semaphore mySemaphore = new Semaphore(100,100);
try {
  mySemaphore.WaitOne(); // This will block until a slot is available.
  DosomeDatabaseLogic();
} finally {
  mySemaphore.Release();
}

你能发布一些代码吗?了解如何创建和安排任务会很有帮助。任务的创建大致如下:Task.Factory.StartNew(()=>ProcessItem(item));我没有对任务对象进行足够的跟踪,以限制任务的创建(如果您正是这么想的话)。不是所有的任务都需要数据库工作,也不是所有的数据库工作都使用同一个数据库(oracle、mssql等有许多不同的数据库)。这是为了处理负载激增,还是请求总是来得太快而无法处理?如果是后者,你将不得不承认失败,并在某个时刻返回失败。这是负载激增,是的。然而,这些相同的浪涌在过去是用1个连接处理的,没有错误。我不认为浪涌会“太大”,无论如何我都会承认失败。我正在使用Task.Factory.StartNew()创建我的任务。我没有看到一个重载,它接受ParllelOptions作为参数。上述方法假设有一组可用的
item
对象来创建
ProcessItem(item)
任务。这可能无法代表您的场景。上面的机制使用了
Parallel.ForEach()
来代替
Task.Factory.StartNew()
+1,我遇到了Andy发布的类似问题,我正在使用Parallel.ForEach。这对我很有帮助。直到你现在提到,我才知道有这样的选择。我确实提到我在问题中考虑过这一点,但是我忍不住觉得这是一种不好的处理方式。它肯定会在短期内解决问题,但真正的问题可能是服务对处理工作的贪婪?我忍不住动摇了这样一个想法:使用1个连接,即使有点慢,也能完美地工作。处理速度的提高太大了,无法回到一个连接。我喜欢这个,我没有意识到它(可能)有那么简单。我会尝试一下,然后再报告。当然,这样做会有一些危险。特别是,信号量不知道可重入性,因此如果DoSomeDatabaseLogic()调用可以递归地尝试重入信号量,则可能会出现死锁。例如,假设100个线程已经在使用信号量插槽。然后,每个线程都尝试重新进入,并阻止等待空闲插槽(这永远不会发生)。您需要注意这种可能性并仔细编写代码。我在这里使用的DoSomeDatabaseLogic部分是一个工作单元,目前是线程安全的,所以我认为这不应该是一个问题。在我的测试场景中,我尝试了这个信号量,它确实在工作。这就是我要找的,谢谢!