【问题标题】:How do you best offload a database insert, so a web response is returned quicker?您如何最好地卸载数据库插入,以便更快地返回 Web 响应?
【发布时间】:2009-12-09 20:02:51
【问题描述】:

设置

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

我已经尽可能地优化了代码,以使响应尽可能快。但是,在将响应发送回 Web 客户端之前,数据库保持打开的时间仍然使连接保持打开的时间比我想要的要长。

代码看起来基本上是这样的,顺便说一句,它是 ASP.NET MVC,使用实体框架,在 IIS 7 上运行,如果这很重要的话。

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 块中的所有内容包装在另一个线程中,以使数据库异步插入,但不知道这是否是将响应释放回客户端的最佳方式。

如果你想实现这个目标,你会推荐什么?

【问题讨论】:

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


    【解决方案1】:

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

    如果您只想插入一条日志记录(访问者/url 跟踪内容),那么最简单的解决方案是使用 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 池线程,除了等待来自 DB 的响应之外什么都不做

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

    如果第一个问题是真实的,那么没有线程和/或生产者/消费者魔法可以减轻它。如果您确实存在传入速率与写入速率可扩展性的问题(“待处理”队列在内存中增长),则您必须使 DB 层中的写入更快(更快的 IO,特殊的日志刷新 IO)和/或您必须聚合写道。无需记录每个请求,只需增加内存计数器并定期将它们作为聚合写入。

    【讨论】:

    • 我知道我正在做的事情会有一个限制点,肯定会有一个瓶颈。但我希望在我能承受的范围内尽可能地摆脱这个瓶颈。因为在此之后的下一个解决方案将是基于云的解决方案。我希望将这一点延迟足够长的时间,让 Azure 成为生产代码的可能性。
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      创建一个单独的线程来监控全局内存队列。让您的请求将其信息放入队列并返回,然后线程将项目从队列中取出并将其发布到数据库。

      在重负载下,如果线程滞后于请求,你的队列将会增长。

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

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

      更正式的机制是使用一些实际的中间件消息传递系统(Java 领域的 JMS,不知道 .NET 中的等价物,但肯定有一些东西)。

      【讨论】:

      • SQL Service Broker 在 OP 的环境中是等效的——它提供可扩展性、队列可靠性、有保证的消息排序和传递。 OTOH,我认为使用分析数据可能有点过头了。
      • @Ben:如果数据写入至关重要(例如金融交易),则确实需要 SSB,如rusanu.com/2009/08/05/asynchronous-procedure-execution。如果目的只是写访问跟踪日志,那么 SSB 就大材小用了,因为发送消息的成本实际上大于插入。
      • @Remus:取决于插入,不是吗?不管怎样,你是对的:这不是去这里的路。
      【解决方案4】:

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

      以这个场景为例:

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

      您还需要通过启动一个新线程而不是保存到数据库来检查您节省了多少毫秒。

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

      【讨论】:

      • 不,存储的数据是被动数据。如果它在传输过程中丢失,这并不是什么大问题,因为这是用于统计分析。话虽如此,我也想尽可能少地丢失数据。
      【解决方案5】:

      在我花很多时间进行优化之前,我会确定时间的去向。像这样的连接有很大的延迟开销(检查this out)。只是为了笑,让您的服务成为 NOP,看看它的表现如何。

      在我看来,“异步”需要在客户端上 - 它应该触发对您的服务的调用并继续前进,尤其是因为它不关心结果?

      我还怀疑,如果 NOP 性能在您的 LAN 上是可以接受的,那将是另一回事。

      【讨论】:

      • 即使调用是在客户端异步的,这不是一个坏主意,服务器应该尽快返回——因为除非客户端在与服务器通信后显式关闭连接,异步进程将保持通道打开,直到响应通过。还是不好。
      • @Ben:非常好的观点。我担心的是在我们确定问题出在哪里之前应用复杂性。过去几年对分布式系统的体验让我大开眼界,95% 的时间我发现即使将服务器处理降低到 0 仍然会导致客户端性能无法接受。
      • 在客户端上以异步方式进行调用。基本上客户端加载javascript,并且带有上述方法的src的img标签被添加到DOM,它发出请求并给我所有的数据。所以客户端并没有停止,它只是在等待图像下载,这会占用一个非常重要的浏览器连接。另外,我的服务器无法处理更多请求。
      • @n8wrl 抱歉,如果这一点不清楚,这是在公共互联网上用于获得对客户浏览器上某些插件的专业洞察。
      猜你喜欢
      • 2016-01-06
      • 1970-01-01
      • 2018-09-16
      • 2011-02-01
      • 1970-01-01
      • 2012-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多