【问题标题】:Connections issue while interactiing with SQL from Azure Function从 Azure Function 与 SQL 交互时出现连接问题
【发布时间】:2020-11-25 04:28:09
【问题描述】:

我们正在使用 Azure Function 与 Azure SQL Db 对话。我们使用最新版本的 Azure 函数 3.1 以及 .net core 3 和 EF core。

最近我们在日志中看到以下错误消息 Microsoft.Data.SqlClient.SqlException (0x80131904): Resource ID : 1. 弹性池的请求限制为 420 并且已达到。请参阅“http://go.microsoft.com/fwlink/?LinkId=267637”以获得帮助。 我们还查看了应用洞察,发现在此期间连接数达到了 1400 左右。

我们在启动文件中使用以下语句将 DbContext 添加到服务中。

    builder.Services.AddDbContextPool<OurDbContext>(options =>
    {
        options.UseSqlServer("connectionstring");
    });

OurDbContext 类具有以下构造函数,

public OurDbContext(DbContextOptions<OurDbContext> options)
    : base(options)
{
}

然后我们将 OurDbContext 类注入到不同的存储库中,这些存储库使用此上下文与 SQL 对话。 类似于下面:

public class Repo : IRepo
{
  public Repo(OurDbContext ourDbContext)
  {

  }
  
  public async Task AddAsync(Entity entity)
  {
    ourDbContext.AddAsync(entity);
    ourDbContext.SaveChangesAsync()
  }
}

我们将这些 repos 注入到 Function 类中并调用上述方法,例如

await _repo.AddAsync()

由于打开的连接数很多,您是否发现上述结构中存在任何问题?

我相信由于某种原因,一旦函数完成执行,上下文/连接就不会被释放。

正在考虑对 dbContext 使用 using 语句,以便在我们完成 db 查询执行后立即处理它。但是,我不确定我们是否可以在启动类中使用 AddDbContextPool,因为它需要选项。此外,OurDbContext 绑定到此选项。可能必须在 OurDbContext 类中使用我不知道如何与 AddDbContextPool 一起使用的 onConfiguring 方法?

任何建议都会非常有用。谢谢!

更新 1 更新了评论中与问题相关的更多细节: 以下是我的方法:

public async Task<List<EntityXyz>()
{
    var xyzToBeUpdated = new EntityXyz()
    {
        mapping of few fields;
    }
    
    OurDbContext.Update(xyzToBeUpdated);
    
    var xyzToBeAdded = new EntityXyz
    {
        mapping of few fields;
    }
    
    await OurDbContext.AddRangeAsync(xyzToBeAdded);

    await OurDbContext.SaveChangesAsync();

    xyzToBeAdded.Add(xyzToBeUpdated);
    return xyzToBeAdded;
}

【问题讨论】:

  • 也许你确实有那么多并发用户或设备调用你的函数,在这种情况下你要么需要扩展,要么扩展你的资源

标签: entity-framework asp.net-core .net-core entity-framework-core azure-functions


【解决方案1】:

您的 DbContext 实例和/或处理它们是不是您的问题。

弹性池的请求限制为420,已达到

SQL Azure 术语中的request 是单个执行的Query,不要将其与与 DbContext 连接大致相关的Sessions 混淆。

  • 我粗略地说是因为连接池发生在幕后。

这篇文章通俗易懂地说明了一切:https://techcommunity.microsoft.com/t5/sql-server-support/tasks-workers-threads-scheduler-sessions-connections-requests/ba-p/333990

错误只是说明您有太多并发查询针对当前订阅级别的数据库操作。

这是一个使用 Azure Functions逻辑应用 和其他 微服务 架构的简单场景,尤其是在您有工作流的情况下涉及可能相互调用的长链服务或一条输入消息生成许多输出消息的序列。

您应该分析通过您的解决方案的消息流,例如,当您达到资源限制时,每个函数有多少个实例正在运行 - 谁是罪魁祸首?是什么触发了这个函数被如此频繁地调用?

我使用以下简单查询来获取当前正在运行的实际请求的快照,实际的 SQL 语句可用于跟踪相关代码,但看到太多或意外的主机可能表明横向扩展场景已经消失猖獗:

SELECT
    SUBSTRING(ST.text, (QS.statement_start_offset/2) + 1,
    ((CASE statement_end_offset
        WHEN -1 THEN DATALENGTH(st.text)
        ELSE QS.statement_end_offset END
            - QS.statement_start_offset)/2) + 1) AS statement_text
       , sess.login_name, sess.login_time, sess.host_name
       , conn.client_net_address
       , QS.session_id, QS.connection_id, QS.command, QS.blocking_session_id, QS.deadlock_priority, QS.estimated_completion_time
     FROM sys.dm_exec_requests AS QS
     -- Join on connections for specifics about how the connection was established
     INNER JOIN sys.dm_exec_connections conn ON conn.connection_id = QS.connection_id
     -- Join on sessions to get the login name
     INNER JOIN sys.dm_exec_sessions sess ON sess.session_id =QS.session_id
     -- parse the SQL text for the running query
     CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) as ST
ORDER BY start_time DESC;

应该总是在列表中看到这个正在运行的查询。如果您当前达到最大值,请运行几次,直到您挤进去(您的应用程序应该会因为异常而开始退出)


可能导致高并发查询率的常见运行时/设计场景:

在没有分析结果的情况下,此类问题的原因大致可分为以下几类:

延迟加载数据库上下文
有时这只是通过启用延迟加载的 DbContext 或 Data Repos 引起的。延迟加载可以极大地简化编码,尤其是在逻辑登录链中,因为您不需要提前知道要检索到内存中的记录和字段。但是,在一个有许多用户或应用程序的许多实例调用同一个数据库的系统中,就像 Azure Functions 通常的情况一样,简单的逻辑循环可能会导致数据在某个位置被重新查询。比您预期的要高得多,尤其是在您使用异步或并行处理时。

  • 对于服务/功能架构,将延迟加载视为延迟编程...急切加载将减少对数据库的调用,精确到您期望的代码发生。

一长串相互关联的异步事件
如果你的Functions调用了其他的服务或者可能触发了其他的函数,而这些函数又可能触发了其他的……尽量减少需要调用数据库的场景。如果在管道中您看到许多相同的请求,或者您知道每个函数调用都有一个 init 进程在处理之前从数据库中加载记录或消息,请考虑更改逻辑,以便第一次调用检索数据并传递一些或所有这些数据都用于下一步。

  • 请记住,异步处理有可能并发运行多个进程,也许您可​​以通过将一些逻辑移动到更同步的处理来减少并发。
  • 请参阅关于Event HubsQueues 的下一点...

尝试实时消息/遥测处理:
这在物联网项目中很常见,但在很多地方都会出现,我们经常感到有必要在需要时立即完成操作。如果您确实收到数百个对您的函数的合法并发请求,那么您应该考虑使用Event HubsQueues 将您的函数的执行与其请求分离。

  • 一种简单的方法是更改​​当前函数,使其将传入请求放入队列(如果您需要支持更高频率或多级并发,则放入 Hub)。然后你将之前的逻辑移动到一个新的函数中,该函数对 QueueEventHub 触发器 进行操作,这些触发器机制允许你配置最大并发进程数和批处理大小有效处理的消息数量限制了您的流程管道,让您可以控制在任何给定时间访问数据库的查询数量。

自定义或实时记录方案:
如果您已经实现了将请求、状态或异常记录到同一数据库(或同一弹性池中的数据库)的自定义日志记录例程,那么这些操作很容易超出您的请求限制,请考虑记录到不同的数据库或使用日志记录框架缓存日志并定期将它们刷新到数据库,NLog 与任何其他功能一样好,但您还应该考虑使用 Azure App Insights,因为您已经集成到 Azure 资源。

  • 在正常负载下,您可能不会注意到任何问题,但是如果您积极地记录异常,一旦您开始达到 DTUrequest 就会限制每个新请求time 将记录异常,更糟糕的是,异常记录本身也可能失败并触发另一个日志......(我已经看到它发生了)这可能会迅速导致对 Db 的指数级多余请求,从而进一步扼杀您的应用程序

积极监控/轮询:
想到的最后一个场景是,如果您的扩展应用程序具有仪表板或报告或 UI 或自定义队列或计时器模式,这意味着某些东西经常(可能在计时器上)对数据库运行查询。 它可能根本不是你的函数......在可能的情况下,这种逻辑应该受到限制,以便你的代码有一个实例来执行轮询并协调其他操作。

  • 轮询代码通常很容易转换为基于Azure Queue 的处理,轮询队列比轮询数据库效率更高,并且不计入您的请求计数。

  • 如果大部分高并发请求是只读的,那么您还可以利用 Azure 中数据库的只读替代品,将读取请求完全卸载到不同的数据连接。

【讨论】:

  • 谢谢。这是一个很好的解释。看起来我们的组织使用 SQL 弹性池。最近开始的这个项目使用 Azure SQL。我目前正在研究 SQL 弹性池。而且,这似乎是由于不同的应用程序调用,使用相同的弹性池引起的。因为,我们使用应用洞察从 azure 函数进行日志记录,任何想法或是否有任何特定指标通过我们可以找到从我们的函数打开并在给定的一天/分钟在 App 洞察中关闭的 SQL 连接?上述指标快照提供了各种连接,如 httpClient、SQL、redis 等。
  • 我只在一个项目中使用了弹性池,这带来了不同的挑战,但建议的效果是一样的。如果您有多个项目或类型的用户访问同一个数据库,我会为每种工作负载类型分配不同的 SQL 用户帐户,这将有助于在应用洞察力中找出导致工作负载的 用户
  • 不要关注 connections,你的问题是 concurrent queries 这是非常短暂的。洞察力当然可以提供帮助,但您需要首先了解解决方案的机制,您实施了什么来限制对数据库的请求?听起来您的设计只是允许所有内容同步或实时并行。还要考虑 SQL Intelligence 提要 - docs.microsoft.com/en-us/azure/azure-sql/database/…
  • 是的,这似乎与代码问题无关,而是对同一个弹性池的请求过多。因此,我已将答案标记为正确。
  • System.ObjectDisposedException:无法访问已处置的对象。此错误的一个常见原因是释放从依赖注入中解析的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在上下文上调用 Dispose() 或将上下文包装在 using 语句中,则可能会发生这种情况。如果你使用依赖注入,你应该让依赖注入容器负责处理上下文实例。对象名称:'OurDbContext'。在 Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
猜你喜欢
  • 1970-01-01
  • 2020-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-25
  • 1970-01-01
  • 2020-10-02
  • 1970-01-01
相关资源
最近更新 更多