【问题标题】:Issues with connections to database under low load (dotnet core)低负载下与数据库的连接问题(dotnet core)
【发布时间】:2019-02-12 13:06:24
【问题描述】:

我正在尝试对一个基本 API 进行负载测试,但我开始遇到一些来自数据库连接的奇怪问题。 我现在已将其范围缩小到 SQL 连接本身。 (我使用SELECT 1 仅用于测试连接)

在非常低的负载(每秒 15 次调用)下,一切都按预期工作。

在低负载(每秒 25 个调用)下,前 4-5 个调用以正常的速度返回,然后迅速减速。由于池中没有连接,大量调用超时。

在中等负载(每秒 50 次调用)下,一切都完全锁定,没有任何回复。我开始得到一些奇怪的东西,比如A network-related or instance-specific error occurred while establishing a connection to SQL Server.。无法再次从池中获取连接。

服务器上的exec sp_who2 也显示没有来自 dotnet 的连接。

更糟糕的是,从这种情况中恢复的唯一方法是退回整个服务。

我已经排除了服务器本身,因为这发生在强大的本地 SQL 服务器、azureSql 数据库和在 docker 上运行的本地服务上。

 int selected = 0;
 var timer = Stopwatch.StartNew();
 using (SqlConnection connection = CreateNewConnection())
 {
      try
      {
          connection.Open();
          selected = connection.QueryFirst<int>("SELECT 1");
          timer.Stop();
      }
      catch (Exception e)
      {
          Console.WriteLine("Failed connection");
          Console.WriteLine("fatal    " + e.Message);
          responseBuilder.AddErrors(e);
      }
      finally
      {
          connection.Close();
      }
 }

 responseBuilder.WithResult(new {selected, ms = timer.ElapsedMilliseconds});

我什至尝试过手动处理并强制关闭连接以了解发生了什么。

这是运行 dotnet core 和 dapper(即使没有 dapper,我也会遇到同样的问题)

我还尝试将最大连接池限制提高到 1000 之类的荒谬数字,但没有任何效果。

编辑

在尝试了更多之后,我决定尝试使用 Postgres。这在每秒超过 1k 次调用时完美运行。 我是否在 sql server 本身中遗漏了一些东西?还是在连接上?

需要指出的是,这些是霰弹枪呼叫。因此,批处理会尽快启动,然后等待每个请求返回。

这也是使用 linux(环境是 docker k8s)

有人想知道连接是如何创建的

    private IDbConnection CreateNewConnection()
    {
        var builder = new SqlConnectionStringBuilder()
        {
            UserID = "sa",
            Password = "012Password!",
            InitialCatalog = "test",
            DataSource = "localhost",
            MultipleActiveResultSets = true,
            MaxPoolSize = 1000
        };
        return new SqlConnection(builder.ConnectionString);
    }

另一个说明

Not shotgunning(等待上一个调用完成,然后再发送另一个)似乎有足够的吞吐量。这似乎是同时处理太多请求的问题

版本信息 点网2.1.401 SqlClient4.5.1

【问题讨论】:

  • 有一点需要指出,这些是猎枪式的呼叫。因此,批处理会尽快启动,然后等待每个请求返回。
  • 似乎 DBContext 是为调用而创建的,并且在整个会话中都保持不变。您能否显示您知道如何连接数据库的代码以及要连接的服务器以及每次调用都会持久化或实例化的代码
  • @StevenDall 更新问题,不要添加 cmets。至于遗漏什么,我想说您没有提供所有代码,但看起来CreateConnection 正在泄漏连接。使用了连接池,因此您不需要重新打开与服务器的真实连接。当您调用Close() 或退出using 块时,连接被重置并放回池中。如果连接实际上已关闭,您不会收到这样的池错误。 PS:这里不需要.Close(),它会在执行离开using块时被调用。
  • @StevenDall 您没有提及您使用的操作系统或 .NET Core 版本。 Linux 的 SqlClient 库中可能存在错误。另一方面,连接池并不是新事物,当您考虑重用 same 连接时,每秒 25 次调用非常低。除非连接字符串禁用连接池?
  • 有一个Github issue about a connection pooling bug 影响了所有操作系统,并已在 SqlClient 4.5.0 中修复。也许这是一个回归,或者一个新的错误?讨论表明问题是由ThreadPool 的不当使用引起的。驱动程序在没有实际打开连接的情况下被卡住,这可以解释为什么您在服务器端看不到任何连接。您可能应该创建一个最小程序并尝试在 Windows 中使用它。

标签: c# sql-server linux .net-core


【解决方案1】:

我可以验证一些可疑的事情正在发生,但它可能没有汇集。我创建了一个控制台应用程序,并在同一个机器上从 Windows 控制台和 WSL 控制台运行它。通过这种方式,我能够从相同的客户端但不同的操作系统/运行时运行相同的代码。

在 Windows 上,即使使用荒谬的 500 DOP,每个连接也只需不到一毫秒:

985 : 00:00:00.0002307
969 : 00:00:00.0002107
987 : 00:00:00.0002270
989 : 00:00:00.0002392

WSL 中的相同代码需要 8 秒或更长时间,即使 DOP 为 20!较大的 DOP 值会导致超时。 10 会产生类似于 Windows 的结果。

一旦我禁用 MARS,但性能恢复正常:

983 : 00:00:00.0083687
985 : 00:00:00.0083759
987 : 00:00:00.0083971
989 : 00:00:00.0083938
992 : 00:00:00.0084922
991 : 00:00:00.0045206
994 : 00:00:00.0044566

这仍然比直接在 Windows 上运行慢 20 倍,但在您并排检查数字之前几乎不会注意到。

这是我在两种情况下都使用的代码:

   static void Main(string[] args)
    {
        Console.WriteLine("Starting");

        var options=new ParallelOptions { MaxDegreeOfParallelism = 500 };
        var watch=Stopwatch.StartNew();
        Parallel.For(0,1000,options,Call);
        Console.WriteLine($"Finished in {watch.Elapsed}");
    }

    public static void Call(int i)
    {            
        var watch = Stopwatch.StartNew();

        using (SqlConnection connection = CreateNewConnection())
        {
            try
            {
                connection.Open();
                var cmd=new SqlCommand($"SELECT {i}",connection);
                var selected =cmd.ExecuteScalar();                    
                Console.WriteLine($"{selected} : {watch.Elapsed}");
            }
            catch (Exception e)
            {
                Console.WriteLine($"Ooops!: {e}");
            }
        }
    }

    private static SqlConnection CreateNewConnection()
    {
        var builder = new SqlConnectionStringBuilder()
        {
            UserID = "someUser",
            Password = "somPassword",                        
            InitialCatalog = "tempdb",
            DataSource = @"localhost",
            MultipleActiveResultSets = true,
            Pooling=true //true by default                
            //MaxPoolSize is 100 by default
        };
        return new SqlConnection(builder.ConnectionString);
    }
}

【讨论】:

  • 不幸的是,我需要 MARS,但我现在可以将其关闭。我看看有没有用,谢谢。
  • @StevenDall 为什么需要 MARS?
  • 我们有几个以异步方式运行的连接同时返回数据。每个 http 调用。但是我们也有一些事务,其中一些初始查询同时运行,为使用它们的最后几个查询做准备。由于要求将其全部回滚,因此出现了问题。尝试同时做一些以节省通话时间。关闭 MARS,测试中一切似乎都按预期工作。我们将更改需要更改的内容以暂时解决此问题
  • @StevenDall 你不需要(不应该)使用 MARS 来使用异步查询。同样,.NET Core 中可能存在迫使您使用它的错误。通常,如果您想在同一连接上触发多个异步查询,则需要 MARS。这是一个相当冒险的想法。您不需要它来启动事务,一个接一个地运行一些异步查询,然后提交或回滚事务
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-26
  • 1970-01-01
  • 1970-01-01
  • 2018-01-03
  • 2019-09-13
  • 1970-01-01
  • 2019-12-28
相关资源
最近更新 更多