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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 池式组件-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等待 和 只建立连接不执行任务,时间上的区别,然后选择合适的线程个数。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
22天前
|
存储 关系型数据库 MySQL
MySQL主从复制原理和使用
本文介绍了MySQL主从复制的基本概念、原理及其实现方法,详细讲解了一主两从的架构设计,以及三种常见的复制模式(全同步、异步、半同步)的特点与适用场景。此外,文章还提供了Spring Boot环境下配置主从复制的具体代码示例,包括数据源配置、上下文切换、路由实现及切面编程等内容,帮助读者理解如何在实际项目中实现数据库的读写分离。
MySQL主从复制原理和使用
|
1月前
|
缓存 算法 关系型数据库
Mysql(3)—数据库相关概念及工作原理
数据库是一个以某种有组织的方式存储的数据集合。它通常包括一个或多个不同的主题领域或用途的数据表。
50 5
Mysql(3)—数据库相关概念及工作原理
|
1月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1625 14
|
22天前
|
SQL 关系型数据库 MySQL
Mysql中搭建主从复制原理和配置
主从复制在数据库管理中广泛应用,主要优点包括提高性能、实现高可用性、数据备份及灾难恢复。通过读写分离、从服务器接管、实时备份和地理分布等机制,有效增强系统的稳定性和数据安全性。主从复制涉及I/O线程和SQL线程,前者负责日志传输,后者负责日志应用,确保数据同步。配置过程中需开启二进制日志、设置唯一服务器ID,并创建复制用户,通过CHANGE MASTER TO命令配置从服务器连接主服务器,实现数据同步。实验部分展示了如何在两台CentOS 7服务器上配置MySQL 5.7主从复制,包括关闭防火墙、配置静态IP、设置域名解析、配置主从服务器、启动复制及验证同步效果。
Mysql中搭建主从复制原理和配置
|
30天前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
1月前
|
存储 SQL 关系型数据库
mysql中主键索引和联合索引的原理与区别
本文详细介绍了MySQL中的主键索引和联合索引原理及其区别。主键索引按主键值排序,叶节点仅存储数据区,而索引页则存储索引和指向数据域的指针。联合索引由多个字段组成,遵循最左前缀原则,可提高查询效率。文章还探讨了索引扫描原理、索引失效情况及设计原则,并对比了InnoDB与MyISAM存储引擎中聚簇索引和非聚簇索引的特点。对于优化MySQL性能具有参考价值。
|
3月前
|
SQL 关系型数据库 MySQL
说一下MySQL主从复制的原理?
【8月更文挑战第24天】说一下MySQL主从复制的原理?
62 0
|
8天前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
23 4
|
6天前
|
SQL 关系型数据库 MySQL
12 PHP配置数据库MySQL
路老师分享了PHP操作MySQL数据库的方法,包括安装并连接MySQL服务器、选择数据库、执行SQL语句(如插入、更新、删除和查询),以及将结果集返回到数组。通过具体示例代码,详细介绍了每一步的操作流程,帮助读者快速入门PHP与MySQL的交互。
19 1
|
15天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
81 1