如何减少频繁创建数据库连接的性能损耗?

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

为极速开发出一套某垂直领域电商系统采用最简架构

  • 前端一台Web服务器运行业务代码
  • 后端一台DB服务器存储业务数据

大多系统初生时就是这样只是随业务不但发展变得复杂架构迭代。系统上线后虽用户量不大但运行一切正常。不过领导觉得用户量太少紧急调动运营做了某音的推广。带来大波流量系统访问速度突然开始变慢。

分析日志后发现系统慢原因出在于和DB交互。目前DB调用方式

  • 先获取DB连接
  • 通过该连接从DB查数据
  • 关闭连接
  • 释放DB资源

这就导致每次执行SQL都需重建连接怀疑因频繁建立DB连接耗时过长导致访问慢。为何频繁创建连接会造成响应时间慢

做个测试

tcpdump -i bond0 -nn -tttt port 4490

抓取线上MySQL建立连接的网络包。观察抓包结果

MySQL连接过程

分为如下部分

前三个数据包

第一个数据包是C向S发送的“SYN”包
第二个包是S回给C的“ACK”包以及一个“SYN”包
第三个包是C回给S的“ACK”包
即TCP三次握手。

MySQL服务端校验客户端密码的过程

第一个包是S发给C要求认证的报文
第二和第三个包是C将加密后的密码发送给S的包最后两个包是S回给C认证OK的报文。
整个连接过程4ms969012-964904。

单条SQL执行时间多少

统计一段时间的SQL执行时间发现SQL平均执行时间1ms相比SQL执行MySQL建立连接过程较耗时。
在请求量小时影响不大因无论建立连接 or 执行SQL耗时都ms级。但请求量很大若仍建一次连接只执行一条SQL1s只能执行200次DB查询而DB建立连接时间就占4/5。

咋优化

只需使用连接池将DB连接预先建立好使用时就无需频繁创建连接。调整后发现1s即可执行1000次DB查询查询性能大大提升

用连接池预先建立DB连接

很多连接池

如DB连接池、HTTP连接池、Redis连接池。连接池的核心技术就是连接池管理。
DB连接池有两个关键配置最小连接数和最大连接数控制从连接池中获取连接的流程。若

  • 当前连接数<最小连接数
    则创建新连接处理DB请求
  • 连接池中有空闲连接
    则复用空闲连接
  • 空闲池中无连接 && 当前连接数<最大连接数
    则创建新连接去处理请求
  • 当前连接数≥最大连接数
    则按配置中设定的时间C3P0的连接池配置checkoutTimeout等待旧连接可用
  • 等待超过设定时间
    则向用户抛出错误

某按摩店共10台按摩椅类比最大连接数为节省成本按摩椅很费电平时会保持店里开着4台按摩椅最小连接数其他6台关着。有顾客来时

  • 若平时保持启动的4台按摩椅有空
    直接请他去空闲那台
  • 4台按摩椅都不空
    就新启一台直到10台按摩椅都被用完

10台按摩椅都被用完后咋办告诉用户等会儿大约5分钟等待时间内能空出来然后第11位用户就开等。这就有两个结果若

  • 5min内有空
    顾客直接去空出的那台
  • 5min都没空
    得赔礼道歉顾客有很急只能让他去其他店看看

DB连接池线上推荐

  • 最小连接数 10
  • 最大连接数 20~30

连接的维护问题。有的按摩椅虽然开着但有时会故障数据库一般故障原因

  • DB域名对应IP变更池子的连接还是使用旧IP当旧IP下的DB服务关闭后再使用该连接查询就会报错
  • MySQL wait_timeout参数控制当DB连接闲置多久后DB会主动关闭该连接。该机制对DB使用方无感知所以使用这个被关闭的连接时就会报错

怎么保证启动着的按摩椅一定可用

  • 启动一个线程定期检测连接池中的连接是否可用。如使用连接发送“select 1”命令给DB查看是否会抛异常若抛则将该连接从池移除并尝试关闭。C3P0连接池可这样检测连接是否可用推荐
  • 获取到连接后先校验连接是否可用若可用才执行SQL。比如DBCP连接池的testOnBorrow配置项就是控制是否开启该验证
    该方案在获取连接时会引入多余开销线上尽量关闭测试环境可用用。

总算搞清连接池工作原理。发现某重要接口需访问3次DB这日后很可能成为系统瓶颈。应该可创建多线程并行处理与DB交互速度就能快了。不过高并发阶段频繁创建线程开销很大于是想到使用线程池。

线程池预创线程

JDK1.5的ThreadPoolExecutor类似连接池重要参数

  • corePoolSize
  • maximumPoolSize

JDK线程池会优先把任务放入队列暂存而非创建更多线程适于执行CPU密集型任务why
因为执行CPU密集型任务时CPU繁忙因此只需创建和CPU核数的线程多了反而导致频繁线程上下文切换降低任务执行效率。
所以当 当前线程数>核心线程数线程池不会增加线程而是放在队列里等待核心线程空闲。

Web系统一般大量I/O操作如查DB、缓存。任务执行I/O操作时CPU就空闲这时若增加执行任务的线程数而不是把任务暂存队列就能在单位时间执行更多任务大大提高任务执行吞吐量。所以Tomcat线程池就改造JDK原生线程池当
线程数>corePoolSize

优先创建线程直到线程数到达maximumPoolSize这就适于Web系统大量I/O操作场景。

线程池中使用的队列堆积量也是需监控的重要指标对实时性要求较高的任务该指标很关键。曾遇到过任务被丢给线程池后长时间都未被执行。当时以为代码Bug后排查发现是因为线程池的coreThreadCount和maxThreadCount设置较小导致任务在线程池大量堆积调大这两参数后问题解决。后来就把重要线程池的队列任务堆积量作为重要监控指标。

使用线程池不要使用无界队列也许你觉得使用无界队列任务永远不会被丢弃只要任务对实时性要求不高反正早晚消费完。但大量任务堆积会占用大量内存一旦内存空间被占满就会频繁地触发Full GC造成服务不可用

综上所管理的对象无论是连接还是线程创建过程都很耗时也很耗系统资源。所以我们把它们放在一个池子统一管理以提升性能和资源复用。

这是一种常见的软件设计思想

池化技术

即空间换时间期望使用预先创建好的对象来减少频繁创建对象的性能开销同时还可以对对象进行统一的管理降低对象的使用成本。

缺陷

  • 存储池子中的对象要消耗多余内存如对象没有被频繁使用就造成内存浪费
  • 池子中的对象要在系统启动时就预创建完成一定程度增加系统启动时间

缺陷相比优势瑕不掩瑜只要我们确认要使用的对象在创建时确实较耗时或消耗资源并且这些对象也确实会被频繁创建和销毁就可使用池化优化。

总结

池子的最大值、最小值设置很重要初期可依据经验设置后面还是需要根据实际运行情况调整。
池子中的对象需在使用前预先初始化完成即预热如使用线程池时就要预初始化所有核心线程。若池子未经预热可能导致系统重启后产生较多慢请求。

池化技术核心是一种空间换时间优化方法的实践所以要关注空间占用情况避免出现空间过度使用出现内存泄露或频繁GC。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 数据库