具有持久 HTTP 连接的 IDbConnection 生命周期管理
Posted
技术标签:
【中文标题】具有持久 HTTP 连接的 IDbConnection 生命周期管理【英文标题】:IDbConnection lifecycle management with persistent HTTP connections 【发布时间】:2012-10-28 15:19:28 【问题描述】:当我的 ASP.NET MVC 应用程序(如 SignalR 集线器)中存在持久 HTTP 连接时,我在管理具有范围为 HttpContext
的 StructureMap 的打开数据库连接的生命周期时遇到问题。
我的 DI 容器 StructureMap 将打开的 IDbConnection
注入到多个服务中。为确保这些数据库连接已关闭并正确处理,我在 EndRequest
事件上调用 ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects()
。
这对 MVC 控制器非常有用,直到需要数据库连接的服务被注入到 SignalR 集线器中,该集线器为每个客户端保持持久的 HTTP 连接打开并最终使连接池饱和。
如果我将IDbConnection
限定为单例,则每个应用程序只会打开一个连接并且池不会饱和,但这是a bad idea,以防连接被锁定或超时。
那么也许有一种方法可以为我的 SignalR 集线器自定义数据库连接的范围?我尝试在每个 Hub 方法中解析一个服务实例,但这仍然会在 HttpContext 范围内实例化一个数据库连接,并在调用客户端的集线器连接期间保持打开状态。
当周围有持久的 HTTP 连接时,我应该如何在 HTTP 范围的上下文中使用 StructureMap 管理数据库连接的生命周期?
示例代码
典型服务
public class MyService
private IDbConnection _con;
public MyService(IDbConnection con)
_con = con;
public IEnumerable<string> GetStuff()
return _con.Select<string>("SELECT someString FROM SomeTable").ToList();
典型的 SignalR 集线器
public class MyHub : Hub
private MyService _service;
public MyHub(MyService service)
_service = service; // Oh Noes! This will open a database connection
// for each Client because of HttpContext scope
public Task AddMessage()
var result = _service.GetStuff();
// ...
结构图配置
For<IDbConnection>()
.HybridHttpOrThreadLocalScoped()
.Use(() => BaseController.GetOpenConnection(MyConnectionString));
Global.asax.cs
public class GlobalApplication : System.Web.HttpApplication
public GlobalApplication()
EndRequest += delegate
ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
;
// ...
【问题讨论】:
在每个集线器方法上创建您的服务是一个解决方案吗?没有足够的信息来回答。 SignalR 将管理多少客户?他们多久通信/调用一次服务器方法? 嘿@VladCiobanu,我用关于在每个集线器方法中解析服务实例的注释更新了我的问题。对于这个特定的应用程序,SignalR 管理大约 10-40 个持久客户端(目前),但是如果负载在高峰时间翻倍,这很容易使连接池最大化。我希望对我的问题有一个规范的答案。 我想我已经解决了这个问题,方法是使用GetNestedContainer()
来显式处理服务并打开它使用的连接,从而解决每个 SignalR 集线器方法中的服务实例。
不,看起来嵌套容器只显式处理瞬态实例,而IDbConnection
不能是瞬态的,因为服务层中的 LINQ 查询实现。
已使用瞬态 IDbConnection 的命名实例解决!很快就会发布答案。
【参考方案1】:
在 SignalR 1.0.0 Alpha 中,Hub
的实现 IDisposable
。 SignalR Hub
实例与HttpContext
不同,它是短暂的,因此如果您在Hub
的Dispose
方法中关闭您的IDbConnection
,您不应该不必要地使您的连接池饱和。
【讨论】:
谢谢@halter73。默认注入的IDbConnection
实例的范围为HttpContext
,如果集线器关闭它会影响其他服务。我的解决方案是使用IDbConnection
的瞬态命名实例,该实例在每个 SignalR 方法的嵌套 StructureMap 容器中解析和处置。我不确定这是否是最好的方法,但它可以确保我的连接池永远不会饱和。
这绝对不是最好的方法。当您使用 websockets 时,每次调用都会获得一个集线器实例,但需要一个长时间运行的 http 请求。所以你会泄漏。
用我的解决方案查看新答案,@dfowler。【参考方案2】:
使用临时数据库连接和嵌套 StructureMap 容器的解决方案
首先,在 StructureMap 中配置一个命名的临时数据库连接实例:
For<IDbConnection>()
.Transient() // scope
.Add(x => BaseController.GetOpenConnection(connectionString, IsDebugging()))
.Named("Transient");
确保在您的默认实例之前配置此,否则它将覆盖默认实例。
其次,将IContainer
注入您的 SignalR 集线器,以便您可以构建嵌套的 StructureMap 容器:
public class JobHub : Hub
private readonly IContainer _container;
public JobHub(IContainer container)
_container = container;
public Task DoStuff(string input)
// ...
在您的 SignalR 方法中实例化一个嵌套容器并解析您命名的临时数据库连接:
using (var httpRequestScope = _container.GetNestedContainer())
var transientConnection =
httpRequestScope.GetInstance<IDbConnection>("Transient");
使用.With<IDbConnection>(transientConnection)
确保嵌套容器实例化的服务和存储库使用此连接:
var myService = httpRequestScope
.With<IDbConnection>(transientConnection)
.GetInstance<MyService>();
var result = myService.DoStuff(input);
return Clients.addResult(result);
最后,作用域using (...)
语句将确保您的嵌套容器自行清理,包括数据库连接。
这里的缺点是您为每个 SignalR 方法调用打开和关闭数据库连接,但由于连接是池化的,因此提前释放可能不是那么糟糕。您的里程数应取决于您的 SignalR 请求量。
您也许可以放弃嵌套容器,只需向DependencyResolver.Current
询问命名的连接实例,但您可能必须记住显式关闭每个连接以防止泄漏。
【讨论】:
以上是关于具有持久 HTTP 连接的 IDbConnection 生命周期管理的主要内容,如果未能解决你的问题,请参考以下文章
每个代理而不是每个路由的 Apache HttpClient 4 个持久连接
面试题:每发送一个http请求就要建立一个tcp连接吗(非持久连接/持久连接)