如何最好地卸载数据库插入,以便更快地返回Web响应?

时间:2009-12-09 20:02:52

标签: sql-server asp.net-mvc entity-framework iis-7

设置

我有通过REST接口获取输入的Web服务。 REST调用不会返回任何有意义的数据,因此传入Web服务的任何内容都只记录在数据库中,就是这样。它是我公司内部使用的分析服务,可以对其网页上收到的Web请求进行一些特殊处理。因此,尽可能少的回复时间非常重要。

我已经尽可能地优化了代码,尽可能快地做出响应。但是,在将响应发送回Web客户端之前,数据库保持打开的时间仍会使连接打开的时间超过我的预期。

代码看起来基本上是这样的,就像ASP.NET MVC一样,使用在IIS 7上运行的Entity Framework,如果这很重要的话。

public ActionResult Add(/*..bunch of parameters..*/) {

    using (var db = new Entities()) {
        var log = new Log {
            // populate Log from parameters
        }
        db.AddToLogs(log);
        db.SaveChanges();
    }

    return File(pixelImage, "image/gif");
}

问题

有没有办法将数据库插入卸载到另一个进程,因此几乎立即返回对客户端的响应?

我正考虑将所有内容包装在另一个线程的using块中,以使数据库插入异步,但不知道这是否是将响应释放回客户端的最佳方法。 / p>

如果你想要实现这个目标,你会建议什么?

5 个答案:

答案 0 :(得分:3)

如果请求必须可靠,那么您需要将其写入数据库。例如。如果您的退货意味着“我已经支付了商家”,那么您在实际提交数据库之前就无法退货。如果处理时间很长,则存在基于数据库的异步模式,使用表作为队列或使用内置队列(如Asynchronous procedure execution)。但是,当需要进行繁重且冗长的处理时,这些适用,而不是简单的日志插入。

当您只想插入日志记录(访问者/网址跟踪内容)时,最简单的解决方案是使用CLR的线程池,只需使用queue the work,例如:

...
var log = new Log {// populate Log from parameters}
ThreadPool.QueueUserWorkItem(stateInfo=>{
  var queueLog = stateInfo as Log;
  using (var db = new Entities()) 
  { 
     db.AddToLogs(queuedLog);
     db.SaveChanges(); 
  }
}, log);
...

这很简单,它可以让ASP处理程序线程尽快返回响应。但它有一些缺点:

  • 如果请求的接收速率超过线程池处理速率,则内存队列将增长,直到它将触发应用程序池“循环”,从而丢失所有正在进行的项目(以及温暖缓存和其他好东西) )。
  • 请求的顺序不会保留(可能重要也可能不重要)
  • 除了等待来自数据库的响应
  • 之外,它只使用CLR池线程

通过使用真正的异步数据库调用,通过SqlCommand.BeginExecuteXXX并将连接上的AsynchronousProcessing设置为true,可以解决最后一个问题。不幸的是,AFAIK EF还没有真正的异步执行,所以你不得不求助于SqlClient层(SqlConnection,SqlCommand)。但是这个解决方案无法解决第一个问题,当页面命中率如此之高以至于这个日志记录(=每次点击时写入)成为一个关键瓶颈。

如果第一个问题是真实的,那么没有线程和/或生产者/消费者的魔法可以缓解它。如果你有一个崩溃率与写入率可伸缩性问题('待处理'队列在内存中增长),你必须在数据库层中更快地写入(更快的IO,特殊日志刷新IO)和/或你必须聚合写道。而不是记录每个请求,只需增加内存计数器并定期将它们写为聚合。

答案 1 :(得分:1)

我在过去一年左右的时间里一直在研究多层解决方案,这需要这种功能,而这正是我一直在做的。

我有一个单例,负责基于ITask接口在后台运行任务。然后我只用我的单例注册一个新的ITask并将控制从我的主线程传递回客户端。

答案 2 :(得分:1)

创建一个单独的线程来监视内存队列中的全局。如果您的请求将其信息放在队列中并返回,则线程将该项目从队列中取出并将其发布到数据库。

在负载很重的情况下,如果线程滞后于请求,您的队列就会增长。

此外,如果您丢失了计算机,您将丢失所有未处理的队列条目。

您是否可以接受这些限制,您需要做出决定。

一个更正式的机制是使用一些实际的中间件消息传递系统(JMS在Java领域,dunno相当于.NET,但肯定有一些东西)。

答案 3 :(得分:0)

在我花费大量时间进行优化之前,我确定时间会在哪里。像这样的连接具有显着的延迟开销(检查this)。只是为了咧嘴笑,让你的服务成为一个NOP,看看它是如何表现的。

在我看来,'async-ness'需要在客户端上 - 它应该启动对您服务的调用并继续前进,特别是因为它不关心结果?

我还怀疑,如果NOP的性能在你的局域网上是可以容忍的,那么这将是一个不同的故事。

答案 4 :(得分:0)

取决于:当您返回客户端时,您是否需要100%确定数据是否存储在数据库中?

采取这种情况:

  • 请求来自
  • 线程已启动以保存到数据库
  • 响应发送到客户端
  • 服务器崩溃
  • 数据未保存到数据库

您还需要通过启动新线程而不是保存到数据库来检查保存的毫秒数。

与响应时间的节省相比,增加的复杂性和维护成本可能过高。而且响应时间的节省可能很低,以至于不会被注意到。