• 一次Mysql连接池卡死导致服务无响应问题分析(.Net Mysql.Data 8.0.21)


    在线程递增到106时捕获dump文件,在windbg中分析到,有七十多个线程被阻塞在创建mysql连接的地方,具体调用堆栈如下图:

    查看源码

    当看到调用堆栈,可以看源码分析具体位置做了什么事情。我们只截取重要部分的代码。

    由上图大概可以看到是创建连接时OpenAsync后创建Tcp连接时导致的锁。

     

    //Open方法
    //当开启连接池时,从池子中拿mysql连接。
    if (Settings.Pooling) 
    {
      if (FailoverManager.FailoverGroup != null)
      {
        FailoverManager.AttemptConnection(this, Settings.ConnectionString, out string connectionString, true);
        currentSettings.ConnectionString = connectionString;
      }
    
      MySqlPool pool = MySqlPoolManager.GetPool(currentSettings);
      if (driver == null || !driver.IsOpen)
        driver = pool.GetConnection();
      ProcedureCache = pool.ProcedureCache;
    }
    
    //GetPool方法
    //静态变量,也就是说在一个进程间,都使用这个Pools
    private static readonly Dictionary<string, MySqlPool> Pools = new Dictionary<string, MySqlPool>();
    //通过lock锁,来获取是否缓存过连接
    public static MySqlPool GetPool(MySqlConnectionStringBuilder settings)
    {
      string text = GetKey(settings);
    
      lock (Pools)
      {
        MySqlPool pool;
        Pools.TryGetValue(text, out pool);
    
        if (pool == null)
        {
          pool = new MySqlPool(settings);
          Pools.Add(text, pool);
        }
        else
          pool.Settings = settings;
    
        return pool;
      }
    }
    
    //MySqlPool方法
    //可以看到一个minsize,针对这个看板服务链接字符串中设置为10,也就是说第一次初始换的时候我们需要在一个锁内创建10个mysql连接。
    //这个服务需要连接5数据库实例,也就是说,初始化的时候需要创建50个连接,恐怖如斯。
    //多说一点,其实maxSize没什么作用,如果实际连接数大于了maxSize,连接池还会继续创建新的连接,并不会限制其数量。
    public MySqlPool(MySqlConnectionStringBuilder settings)
    {
      _minSize = settings.MinimumPoolSize;
      _maxSize = settings.MaximumPoolSize;
    
      _available = (int)_maxSize;
      _autoEvent = new AutoResetEvent(false);
    
      if (_minSize > _maxSize)
        _minSize = _maxSize;
      this.Settings = settings;
      _inUsePool = new List<Driver>((int)_maxSize);
      _idlePool = new Queue<Driver>((int)_maxSize);
    
      //看这里初始化最小连接数
      for (int i = 0; i < _minSize; i++)
        EnqueueIdle(CreateNewPooledConnection());
    
      ProcedureCache = new ProcedureCache((int)settings.ProcedureCacheSize);
    }
    
    //CreateNewPooledConnection方法内是创建tcp连接,直接看主要方法。
    //我们可以看到在dnsTask.Wait,这个其实执行很快。
    //主要是创建Tcp连接时比较慢,它根据连接超时时间等待是否连接完成,默认是60s。
    private static Stream GetTcpStream(MySqlConnectionStringBuilder settings, ref MyNetworkStream networkStream)
    {
      Task<IPAddress[]> dnsTask = Dns.GetHostAddressesAsync(settings.Server);
      dnsTask.Wait();
      if (dnsTask.Result == null || dnsTask.Result.Length == 0)
        throw new ArgumentException(Resources.InvalidHostNameOrAddress);
      IPAddress addr = dnsTask.Result.FirstOrDefault(c => c.AddressFamily == AddressFamily.InterNetwork);
      if (addr == null)
        addr = dnsTask.Result[0];
      TcpClient client = new TcpClient(addr.AddressFamily);
      Task task = client.ConnectAsync(settings.Server, (int)settings.Port);      
      
      //主要看这里
      if (!task.Wait(((int)settings.ConnectionTimeout * 1000)))
        throw new MySqlException(Resources.Timeout);
      if (settings.Keepalive > 0)
      {
        SetKeepAlive(client.Client, settings.Keepalive);
      }
      networkStream = new MyNetworkStream(client.Client,true);
      var result = client.GetStream();
      GC.SuppressFinalize(result);
    
      return result;
    }

    产生原因

    看上面的源码你可能就也能想到,如果使用连接池,我们可以把连接字符串中的minSize设置小一点(比如设置为0)和Connection TimeOut设置小一点(5s),我们再次启动程序后,可以看到显著的效果,线程激增的情况会减少,可能重启多次会有一次这种效果。
    在初始化创建连接时,大部分的线程被卡到获取连接的地方,不断有请求进来,线程池里面的线程,就被阻塞,需要创建新的线程执行任务,就导致线程一直递增。

    解决办法
    方法一
    #修改前 server=mysql.rds.aliyuncs.com;port=3306;uid=;password=;character set=utf8mb4;Initial Catalog=wgcapplyvehicledb;pooling=true;min pool size=10;max pool size=100;connect timeout =10;
    #修改后 server=mysql.rds.aliyuncs.com;port=3306;uid=;password=;character set=utf8mb4;Initial Catalog=wgcapplyvehicledb;pooling=true;min pool size=0;max pool size=100;connect timeout =5;
    #或者不使用连接池 server=rds.aliyuncs.com;port=3306;uid=;password=;character set=utf8mb4;Initial Catalog=wgcapplyvehicledb;connect timeout =5;
    方法二
    在上面说的修改连接字符串的方式,虽然减少了出现的情况的几率,但是实际上还是会有阻塞线程的情况。所以推荐使用MySqlConnector这个包(源码地址),支持异步创建连接,就不会出现这个情况了。
     
     
    https://www.cnblogs.com/shamork/p/6636305.html
     
     
     
  • 相关阅读:
    Linux常见问题解决
    使用npm国内镜像
    常用CSS备忘
    如何把JavaScript数组中指定的一个元素移动到第一位
    教你如何将word中的表格完美粘贴到ppt中
    测试开发之路--一个小小工程师的回首
    一篇文章读完50篇摄影教程(托马斯的2016总结)
    李开复推荐的30本创业/管理/互联网必须看的电子书
    摩拜单车深度产品体验报告
    Word2016(2013)怎么从任意页插入起始页码
  • 原文地址:https://www.cnblogs.com/liuqiyun/p/14987941.html
Copyright © 2020-2023  润新知