池式组件-Mysql连接池的原理与实现

本文涉及的产品
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
简介: 池式组件-Mysql连接池的原理与实现

一、为什么要使用连接池?

在一般情况下,客户端向Mysql服务器的连接,要经过以下几个步骤

1.TCP三次握手连接

2.Mysql认证的三次握手

3.真正的SQL执行

4.Mysql的关闭

5.TCP的四次握手关闭

这种方式,网络IO较多,带宽利用率低,每秒的查询次数(QPS)会降低,大量的时间花费在建立连接和关闭连接上面,在关闭连接后会出现大量TIME_WAIT的TCP状态。

使用连接池的好处

使用连接池,只需要在第一次访问的时候去建立连接,之后的访问,都会使用之前创建的连接,这样1.可降低网络开销2.增加连接复用有效减少连接数3.提升性能避免频繁新建与销毁连接4.没有TIME_WAIT状态的问题。

可以通过连接池去管理这些连接。

msql连接是短连接,如果长时间不使用会断开,使用连接池,可以减少连接的数量,过长时间断开的情况减少,就会减少重连情况,提升效率

二、连接池与线程池的关系

当线程池中一个线程中的任务要使用连接,那么连接池会为它分配一个连接对象,使用完后,将连接对象归还致连接池。

一般,线程池中的线程数量与连接池中连接对象数量一致。线程任务执行完成要归还连接对象。

三、连接池的涉及要点

使用连接池首要先连接数据库

1.创建一个连接池(init),并连接到数据库(ip、port、user、password、dbname等),配置最小连接数,最大连接数

2.管理连接池的连接,比如list

3.获取连接对象

4.归还连接对象

5.连接池的名字

四、连接池代码实现

1、连接池构造函数

用于配置数据库连接的内容

// 构造函数
CDBPool::CDBPool(const char *pool_name, const char *db_server_ip, uint16_t db_server_port,
         const char *username, const char *password, const char *db_name, int max_conn_cnt)
{
  m_pool_name = pool_name;
  m_db_server_ip = db_server_ip;
  m_db_server_port = db_server_port;
  m_username = username;
  m_password = password;
  m_db_name = db_name;
  m_db_max_conn_cnt = max_conn_cnt; // 最大连接数
  m_db_cur_conn_cnt = MIN_DB_CONN_CNT; // 最小连接数量(开始先创建最小连接数量个 连接)
}

2、连接池初始化

初始化连接,创建固定个连接对象,加入到空闲队列free_list中,用于后续取用。

//连接池初始化
int CDBPool::Init()
{
  // 创建固定最小的连接数量
  for (int i = 0; i < m_db_cur_conn_cnt; i++)//创建最小连接数个连接放入,空闲队列free_list中 
  {
    CDBConn *pDBConn = new CDBConn(this);//创建连接
    int ret = pDBConn->Init();
    if (ret)
    {
      delete pDBConn;
      return ret;
    }
    m_free_list.push_back(pDBConn);//连接放入空闲队列
  }
  // log_info("db pool: %s, size: %d\n", m_pool_name.c_str(), (int)m_free_list.size());
  return 0;
}

3、请求获取连接

注意要加锁:避免多线程获取到同样的对象,需要保证每个任务获得得连接是唯一的,不允许两个任务共用一个连接

/*
 *TODO: 增加保护机制,把分配的连接加入另一个队列,这样获取连接时,如果没有空闲连接,
 *TODO: 检查已经分配的连接多久没有返回,如果超过一定时间,则自动收回连接,放在用户忘了调用释放连接的接口
 * timeout_ms默认为 0死等
 * timeout_ms >0 则为等待的时间
 */
int wait_cout = 0;
CDBConn *CDBPool::GetDBConn(const int timeout_ms)//获得连接
{
  std::unique_lock<std::mutex> lock(m_mutex);
  if(m_abort_request) 
  {
    log_warn("have aboort\n");
    return NULL;
  }
  if (m_free_list.empty())    // 当没有连接可以用时
  {
    // 第一步先检测 当前连接数量是否达到最大的连接数量 
    if (m_db_cur_conn_cnt >= m_db_max_conn_cnt)
    {
      // 如果已经到达了,看看是否需要超时等待
      if(timeout_ms <= 0)   // 死等,直到有连接可以用 或者 连接池要退出
      {
        log_info("wait ms:%d\n", timeout_ms);
        m_cond_var.wait(lock, [this] 
        {
          // log_info("wait:%d, size:%d\n", wait_cout++, m_free_list.size());
          // 当前连接数量小于最大连接数量 或者请求释放连接池时退出
          return (!m_free_list.empty()) | m_abort_request;
        });
      } else {
        // return如果返回 false,继续wait(或者超时),  如果返回true退出wait
        // 1.m_free_list不为空
        // 2.超时退出
        // 3. m_abort_request被置为true,要释放整个连接池
        m_cond_var.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] {
          // log_info("wait_for:%d, size:%d\n", wait_cout++, m_free_list.size());
          return (!m_free_list.empty()) | m_abort_request;
        });
        // 带超时功能时还要判断是否为空
        if(m_free_list.empty())   // 如果连接池还是没有空闲则退出
        {
          return NULL;
        }
      }
      if(m_abort_request) 
      {
        log_warn("have aboort\n");
        return NULL;
      }
    }
    else // 还没有到最大连接则创建连接
    {
      CDBConn *pDBConn = new CDBConn(this); //新建连接
      int ret = pDBConn->Init();
      if (ret)
      {
        log_error("Init DBConnecton failed\n\n");
        delete pDBConn;
        return NULL;
      }
      else
      {
        m_free_list.push_back(pDBConn);
        m_db_cur_conn_cnt++;
        // log_info("new db connection: %s, conn_cnt: %d\n", m_pool_name.c_str(), m_db_cur_conn_cnt);
      }
    }
  }
  CDBConn *pConn = m_free_list.front(); // 获取连接
  m_free_list.pop_front();  // STL 吐出连接,从空闲队列删除
  // pConn->setCurrentTime();  // 伪代码
  m_used_list.push_back(pConn);   
  return pConn;//返回 连接 
}

4、归还连接

void CDBPool::RelDBConn(CDBConn *pConn)//归还连接
{
  std::lock_guard<std::mutex> lock(m_mutex);
  list<CDBConn *>::iterator it = m_free_list.begin();
  for (; it != m_free_list.end(); it++) // 避免重复归还(虽然不会出现,但尽量避免)
  {
    if (*it == pConn) //重复归还 就break
    {
      break;
    }
  }
  if (it == m_free_list.end())//确实没有归还
  {
    m_used_list.remove(pConn);
    m_free_list.push_back(pConn);
    m_cond_var.notify_one();    // 通知取队列
  } else 
  {
    log_error("RelDBConn failed\n");
  }
}
// 遍历检测是否超时未归还
// pConn->isTimeout(); // 当前时间 - 被请求的时间
// 强制回收  从m_used_list 放回 m_free_list

5、析构连接池

遍历销毁连接,如果有连接还没有返回来,有泄漏问题。 如果真的要销毁连接池

1:先销毁线程池,确保所有任务退出

2:再去销毁连接池

连接需要归还了,才能去销毁

1.资源申请释放的顺序非常重要

2.异步编程时比较容易崩,资源被释放了异步函数可能还在使用

CDBPool::~CDBPool()
{
  std::lock_guard<std::mutex> lock(m_mutex);
  m_abort_request = true;
  m_cond_var.notify_all();    // 通知所有在等待的
  for (list<CDBConn *>::iterator it = m_free_list.begin(); it != m_free_list.end(); it++)  //可能发生泄漏
  {
    CDBConn *pConn = *it;
    delete pConn;
  }
  m_free_list.clear();
}

五、为什么要有连接池名

1.debug的时候,可以通过连接池名字识别连接

2.统一管理连接池中

可以通过连接池名,映射到指定的连接池

Map:连接池名—>连接池

比如:

GetConn("聊天消息连接池")
GetConn("用户信息连接池 ");

同一个接口返回的连接是不一样的。方便管理和使用。

六、重连机制

1.每次操作之前先去测试链路是否通(任务能更大概率执行成功)

本次mysql连接池中就使用这种方法。

2.先去执行任务,再去处理连接不同的问题

redis中重连采用这种办法

string CacheConn::get(string key)
{
  string value;
  if(Init())
  {
    return value;
  }
}

其中CacheConn::Init()

1.检测连接是否初始化

2.如果没有初始化就重新初始化

3.如果已经初始化直接返回

七、对于连接池来说,选择多少线程/连接合适呢?

测试 连接+执行任务+IO等待 和 只建立连接不执行任务,时间上的区别,然后选择合适的线程个数。


相关实践学习
自建数据库迁移到云数据库
本场景将引导您将网站的自建数据库平滑迁移至云数据库RDS。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业务,无需过多担心数据库的管理和维护。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
9月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
自然语言处理 搜索推荐 关系型数据库
MySQL实现文档全文搜索,分词匹配多段落重排展示,知识库搜索原理分享
本文介绍了在文档管理系统中实现高效全文搜索的方案。为解决原有ES搜索引擎私有化部署复杂、运维成本高的问题,我们转而使用MySQL实现搜索功能。通过对用户输入预处理、数据库模糊匹配、结果分段与关键字标红等步骤,实现了精准且高效的搜索效果。目前方案适用于中小企业,未来将根据需求优化并可能重新引入专业搜索引擎以提升性能。
600 5
|
9月前
|
SQL 关系型数据库 MySQL
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
|
关系型数据库 MySQL 数据库
RDS用多了,你还知道MySQL主从复制底层原理和实现方案吗?
随着数据量增长和业务扩展,单个数据库难以满足需求,需调整为集群模式以实现负载均衡和读写分离。MySQL主从复制是常见的高可用架构,通过binlog日志同步数据,确保主从数据一致性。本文详细介绍MySQL主从复制原理及配置步骤,包括一主二从集群的搭建过程,帮助读者实现稳定可靠的数据库高可用架构。
802 9
RDS用多了,你还知道MySQL主从复制底层原理和实现方案吗?
|
SQL 存储 关系型数据库
MySQL主从复制 —— 作用、原理、数据一致性,异步复制、半同步复制、组复制
MySQL主从复制 作用、原理—主库线程、I/O线程、SQL线程;主从同步要求,主从延迟原因及解决方案;数据一致性,异步复制、半同步复制、组复制
1576 11
|
存储 缓存 关系型数据库
MySQL进阶突击系列(08)年少不知BufferPool核心原理 | 大哥送来三条大金链子LRU、Flush、Free
本文深入探讨了MySQL中InnoDB存储引擎的buffer pool机制,包括其内存管理、数据页加载与淘汰策略。Buffer pool作为高并发读写的缓存池,默认大小为128MB,通过free链表、flush链表和LRU链表管理数据页的存取与淘汰。其中,改进型LRU链表采用冷热分离设计,确保预读机制不会影响缓存公平性。文章还介绍了缓存数据页的刷盘机制及参数配置,帮助读者理解buffer pool的运行原理,优化MySQL性能。
|
SQL 存储 关系型数据库
MySQL进阶突击系列(05)突击MVCC核心原理 | 左右护法ReadView视图和undoLog版本链强强联合
2024年小结:感谢阿里云开发者社区每月的分享交流活动,支持持续学习和进步。过去五个月投稿29篇,其中17篇获高分认可。本文详细介绍了MySQL InnoDB存储引擎的MVCC机制,包括数据版本链、readView视图及解决脏读、不可重复读、幻读问题的demo演示。
|
7月前
|
缓存 关系型数据库 BI
使用MYSQL Report分析数据库性能(下)
使用MYSQL Report分析数据库性能
484 158
|
7月前
|
关系型数据库 MySQL 数据库
自建数据库如何迁移至RDS MySQL实例
数据库迁移是一项复杂且耗时的工程,需考虑数据安全、完整性及业务中断影响。使用阿里云数据传输服务DTS,可快速、平滑完成迁移任务,将应用停机时间降至分钟级。您还可通过全量备份自建数据库并恢复至RDS MySQL实例,实现间接迁移上云。
|
7月前
|
关系型数据库 MySQL 数据库
阿里云数据库RDS费用价格:MySQL、SQL Server、PostgreSQL和MariaDB引擎收费标准
阿里云RDS数据库支持MySQL、SQL Server、PostgreSQL、MariaDB,多种引擎优惠上线!MySQL倚天版88元/年,SQL Server 2核4G仅299元/年,PostgreSQL 227元/年起。高可用、可弹性伸缩,安全稳定。详情见官网活动页。
1188 152

推荐镜像

更多