美文网首页
07 池化技术:如何减少频繁创建数据库连接的性能损耗?

07 池化技术:如何减少频繁创建数据库连接的性能损耗?

作者: 浥羽醉悠扬 | 来源:发表于2020-02-11 19:23 被阅读0次

在人手紧张、时间不足的情况下,为了能够完成任务,一般会采用最简单的架构:前段一台web服务器运行业务代码,后端一台数据库服务器存储业务数据。

但是当用户出现大幅度增长时,系统的访问速度开始变慢。这时候慢的原因大概率出现在和数据库的交互上。因为数据库的调用方式是先获取数据库的连接,然后依靠这条连接从数据库中查询数据,最后关闭连接释放数据库资源。在这种调用方式下,每次执行SQL都需要重新建立连接。

为什么频繁的创建连接会遭横响应时间慢呢?看一个实际测试:

用"tcpdump -i bond0 -mm -tttt port 4490"命令抓取线上MySQL建立连接的网络包来做分析,从抓包结果来看,整个MySQL的连接过程可以分为两部分:
第一部分是前三个数据包。第一个数据包是客户端向服务端发送的一个“SYN”包,第二个包是服务端返回客户端的“ACK”包以及另一个“SYN”包,第三个包是客户端回给服务端的“ACK”包,显而易见,这是一个TCP三次握手的过程。
第二部分是MySQL服务端校验客户端密码的过程。其中第一个包是服务端发给客户端要求认证的保温,第二和第三个包是客户端将加密后的密码发送给服务端的包,最后两个包是服务端回给客户端认证OK的报文。从图中,可以看到整个连接过程大概消耗了4ms(969012-964904)。

image.png
相较于SQL的执行,MySQL建立连接的过程是比较耗时的,这在请求量小的时候影响不大,可是请求量很大时,如果按照原来的方式建立一次连接只执行一条SQL的话,1s只能执行200次数据库的查询,而数据库建立连接的时间占了其中绝大部分。

那么该如何解决呢?
需要使用连接池将数据库连接预先建立好,这样在使用的时候就不需要频繁的创建连接了。调整之后,1s就可以执行1000次的查询,性能大大提升。

用连接池预先建立数据库连接
其实在开发过程中会用到很多的连接池,比如数据库连接池,HTTP连接池,Redis连接池等。而连接池的管理是连接池设计的核心。

数据库连接池有两个最重要的配置:最小连接数和最大连接数,他们控制着从连接池获取连接的流程:

  • 如果当前连接数小于最小连接数,则创建新的连接处理数据库的请求;
  • 如果连接池中有空闲连接则复用空闲连接;
  • 如果空闲池中没有连接并且当前连接数小于最大连接数,则创建新的连接处理请求;
  • 如果当前连接数已经大于等于最大连接数,则按照配置中设定的时间等待旧的连接可用;
  • 如果等待超过了设定时间,向用户抛出错误。

对于数据库连接池,一般线上建议最小连接数控制在10左右,最大连接数控制在20-30即可。

在这里需要注意池子中连接的维护问题,一般情况下,故障原因可能有以下几种:
1.数据库的域名对应的IP发生了变更,池子的连接还是使用旧的IP,当旧的IP下的数据库服务关闭后,再使用这个连接查询就会发成错误;
2.MySQL有个参数是“wait_timeout”,控制着当数据库连接闲置多长时间后,数据库会主动的关闭这条连接。这个机制对于数据库使用方式无感知的,当我们使用这个被关闭的连接时就会发生错误。

那么如何保证连接一定是可用的呢?
1.启动一个线程来定期检测连接池中的连接是否可用,比如使用连接发送“select 1”给数据库看是否会抛出异常,如果抛出异常则将这个连接从连接池中移除并且尝试关闭。目前C3P0连接池可用采用这种方式来检测连接是否可用。
2.在获取到连接之后,先校验连接是否可用,如果可用再执行SQL语句。比如DBCP连接池的testBorrow配置项,就是控制是否开启这个验证,这种方式在获取连接时会引入多余的开销,在线上系统中尽量不要开启,在测试服务器上可以使用

至此连接池的工作原理已经清晰 。
假如在一个非常重要的接口中,需要访问3次数据库。根据经验判断,未来肯定会成为系统瓶颈。
进一步想,你觉得可以创建多个线程来并行处理与数据库之间的交互,这样速度就快了吗,但是频繁的创建线程的开销也会很大,于是顺着之前的思路继续想,猜测到了线程池。

用线程池预先创建线程
JDK 1.5中引入的ThreadPoolExecutor就是一种线程池的实现,它有2个重要的参数:coreThreadCount和maxThreadCount,这两个参数控制着线程池的执行过程。以下是原理过程:

image.png
image.png

这个任务处理流程看似简单,实际上有很多坑,使用时一定要注意。

首先,JDK实现这个线程池优先吧任务放入队列暂存起来,而不是创建更多线程,它比较适用于执行CPU密集型的任务,因为执行CPU密集型任务时CPU比较繁忙,因此只需要创建和CPU核心数相当的线程就好了,多了反而会造成线程上下文切换,降低任务执行效率。所以当当前线程数超过核心线程数时,线程池不会增加线程,而放在队列里等待线程空闲下来。

但是我们平时开发的Web系统通常有大量的IO操作,比方说查询数据库、查询缓存等。任务在执行IO操作时CPU就空闲了下来,这时如果增加执行任务的线程数而不是把任务暂存在队列中,就可以在单位时间内执行更多的任务,大大提高了任务执行的吞吐量。所以Tomcat使用的线程池就不是JDK原生的线程池,而是做了一些改造,当线程数超过coreThreadCount之后会优先创建线程,直到线程数达到max,这样就比较适合于Web大量IO操作的场景了。

其次,线程池中使用队列的堆积量也是需要监控的重要指标,对于实时性要求比较该的任务来说,这个指标尤为关键。
(在实际项目中遇到过任务被丢给线程池之后,长时间都没有被执行的诡异问题。经过排查发现,是因为线程池的coreThreadCount和max设置的比较小,导致任务在线程池里有大量堆积,调大了这两个参数后问题解决。任务堆积量是一个重要监控指标。)

最后,如果你使用线程池一定不要使用无界队列(没有设置固定大小的队列)。也许你觉得使用了无界队列后,任务就永远不会被丢弃,只要任务对实时性要求不高,总有消费完的一天,但是大量的任务堆积会占用大量的内存空间,一旦内存空间被沾满就会频繁的触发Full GC,造成服务不可用。

回顾一下这两种技术,会发现它们有一个共同点:它们所管理的对象没无论是连接还是线程,它们的创建过程都比较耗时,也比较消耗系统资源。所以我们把它们放在一个池子里统一管理起来,以达到提升性能和资源复用 的目的。

这是一种常见的软件设计思想,叫做池化技术。核心思想是空间换时间,期望使用预先创建好的对象来减少频繁创建对象的性能开销,同时还可以对对象进行统一的管理。降低了兑现更多使用成本。

池化技术也存在一些缺陷,比如说存储池中的对象肯定需要消耗多余的内存,如果对象没有被频繁的使用,会造成内存上的浪费。再比如说,池子中的对象需要在系统启动的时候就预先创建完成。这在一定程度上增加了系统启动时间。

小结

  • 池子的最大值和最小值的设置很重要,初期可以跟经验来设置,后面还需要根据实际运行做调整。
  • 池中的对象需要在使用之前预先初始化完成,称之为预热,比如说,使用线程池时就需要预先初始化所有的核心线程。如果池子未经预热可能会导致系统重启后产生比较多的慢请求。
  • 池化技术核心是一种空间换时间的实践,所以要关注空间占用情况,避免出现空间过度使用出现内存泄漏或者频繁的垃圾回收等问题。

node.js实现

mysql.js

//连接数据库
var mysql = require('mysql');
var pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database:'baseName'
});

module.exports = function(sql, callback) {
  pool.getConnection(function(conn_err, conn) {
    if(conn_err) {
      callback(err,null,null);
    } else {
      conn.query(sql, function(query_err, rows, fields) {
        conn.release();
        callback(query_err, rows, fields);
      });
    }
  });
};


调用
var query = require("./mysql.js");
query(sql, function(err, rows, fields) {
  console.log(rows);
});

相关文章

  • 07 | 池化技术:如何减少频繁创建数据库连接的性能损耗?

    在前面几节课程中,我从宏观的角度带你了解了高并发系统设计的基础知识,你已经知晓了,我们系统设计的目的是为了获得更好...

  • 07 池化技术:如何减少频繁创建数据库连接的性能损耗?

    在人手紧张、时间不足的情况下,为了能够完成任务,一般会采用最简单的架构:前段一台web服务器运行业务代码,后端一台...

  • 线程池原理

    线程池,关注如何缩短频繁线程创建和销毁的时间,通过线程复用技术,减少非核心任务的时间损耗(创建和销毁的时间),提高...

  • 连接池技术

    连接池技术作为创建和管理连接的缓冲池技术,目前已广泛用于诸如数据库连接等长连接的维护和管理中,能够有效减少系统的响...

  • database/sql包连接池解读

    连接池是做网络应用经常用到的一个概念,可以缓存连接减少创建连接的损耗。那么在golang中应该如何做一个连接池,在...

  • 11.数据连接池

    数据连接池的原理就是解决多次连接数据库的性能损耗,建立一个pool,用来存储数据库连接,需要的时候直接从连接池获取...

  • 使用commons-pool2实现FTP连接池

    一. 连接池概述 ​ 频繁的建立和关闭连接,会极大的降低系统的性能,而连接池会在初始化的时候会创建一定数量的连...

  • Golang 全局sql数据库连接

    Golang 如何把sql数据库连接写成全局的,不用每次频繁创建销毁,减少数据库消耗与代码复杂度。 数据库连接通常...

  • 池化技术(JAVA)分析

    简介 池化技术能够减少资源对象的创建次数,提高程序的性能,特别是在高并发下这种提高更加明显。使用池化技术缓存的资源...

  • springboot使用commons-pool2实现对象池

    一. 连接池概述 频繁的创建和销毁,会极大的降低系统的性能,而对象池会在初始化的时候会创建一定数量的对象,每次访问...

网友评论

      本文标题:07 池化技术:如何减少频繁创建数据库连接的性能损耗?

      本文链接:https://www.haomeiwen.com/subject/rcfwxhtx.html