为什么需要数据库连接池
为什么需要连接池
使用数据库直接连接
对 MySQL 多半是进行连接(connection),增删改查并提交(execSQL、commit),关闭连接(close)操作,然后实现业务相关逻辑。其操作也很清晰:
建立连接
发送请求(数据的 CRUD 操作)
关闭连接
数据库连接池
为啥会需要有连接池?其实在业务量流量不大,并发量也不大的情况下,连接临时建立完全可以。但并发量起来,达到百级、千级,其中建立连接、关闭连接的操作会造成性能瓶颈,所以得考虑连接池来优化上述 1 和 3 操作:
取出连接(业务服务启动时,初始化若干个连接,放在连接存储中)
发送请求(当有请求,从连接存储中中取出)
放回连接(执行完毕,连接放回连接存储中)
这里对连接存储的数据结构,并维护连接,就是连接池。
池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
不使用连接池
TCP 建立连接的三次握手(客户端与 MySQL 服务器的连接基于 TCP 协议)
MySQL 认证的三次握手
真正的 SQL 执行
MySQL 的关闭
TCP 的四次握手关闭
可以看到,为了执行一条 SQL,却多了非常多我们不关心的网络交互。
使用连接池
第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行 SQL 语句。
数据库连接池运行机制
从连接池获取或创建可用连接;
使用完毕之后,把连接返回给连接池;
在系统关闭前,断开所有连接并释放连接占用的系统资源;
还能够处理无效连接(原来登记为可用的连接,由于某种原因不可再用,如超时、通讯问题),并能够限制连接池中的连接总数不低于某个预定值和不超过某个预定值。
使用数据库连接池技术的好处
资源重用
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
更快的系统响应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用了现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
统一的连接管理,避免数据库连接泄露
在较为完整的数据库连接池中,可根据预先的连接中超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄露。
连接池的属性
name:表示你的连接池的名称也就是你要访问连接池的地址 url: 数据库的地址 username:登录数据库的用户名 password:登录数据库的密码 maxidle:是最大的空闲连接数,这里取值为 2,表示即使没有数据库连接时依然保持 2 空闲的连接,而从不被清除,随时处于待命状态。maxwait:最大建立连接等待时间。如果超过此时间讲接到异常。设为-1 表示无限制。缺省为 5000,表示 5 秒后超时。maxActive:最大激活连接数,这里取值为 4,表示同时最多有 4 个数据库连接。
连接池的大小
可以很直接的说,关于数据库连接池大小的设置,每个开发者都可能在一环节掉进坑里,事实上呢,大部分程序员可能都会依靠自己的直觉去设置它的大小,设置成 100 ?思量许久后,自顾自想,应该差不多吧?
Oracle 性能小组发布的简短视频,链接地址为 http://www.dailymotion.com/video/x2s8uec,需要 XX 上网观看。
数据库连接池的大小为 2048:每个请求要在连接池队列里等待 33ms,获得连接之后,执行 SQL 需要耗时 77ms, CPU 消耗维持在 95% 左右;数据库连接池的大小为 1024:获取连接等待时长基本不变,但是 SQL 的执行耗时降低数据库连接池的大小为 96:每个请求在连接池队列中的平均等待时间为 1ms, SQL 执行耗时为 2ms.
我们没调整任何东西,仅仅只是将数据库连接池的大小降低了,这样,就能把之前平均 100ms 响应时间缩短到了 3ms。吞吐量指数级上升啊!
测试结果分析
CPU 内核数量
我们不妨想一下,为啥 Nginx 内部仅仅使用了 4 个线程,其性能就大大超越了 100 个进程的 Apache HTTPD 呢?追究其原因的话,回想一下计算机科学的基础知识,答案其实非常明显。要知道,即使是单核 CPU 的计算机也能“同时”运行着数百个线程。但我们其实都知道,这只不过是操作系统快速切换时间片,跟我们玩的一个小把戏罢了。一核 CPU 同一时刻只能执行一个线程,然后操作系统切换上下文,CPU 核心快速调度,执行另一个线程的代码,不停反复,给我们造成了所有进程同时运行假象。其实,在一核 CPU 的机器上,顺序执行 A 和 B 永远比通过时间分片切换“同时”执行 A 和 B 要快,其中原因,学过操作系统这门课程的童鞋应该很清楚。一旦线程的数量超过了 CPU 核心的数量,再增加线程数系统就只会更慢,而不是更快,因为这里涉及到上下文切换耗费的额外的性能。说到这里,你应该恍然大悟了 ……
磁盘 IO
也许你会说,还有内存这一因素?内存的确是需要考虑的,但是比起 磁盘 IO 和 网络 IO ,稍显微不足道,这里就不加了。假设我们不考虑磁盘 IO 和网络 IO,就很好定论了,在一个 8 核的服务器上,数据库连接数/线程数设置为 8 能够提供最优的性能,如果再增加连接数,反而会因为上下文切换导致性能下降。大家都知道,数据库通常把数据存储在磁盘上,而磁盘呢,通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的。读/写头同一时刻只能出现在一个位置,当它需要再次执行读写操作时,它必须“寻址”到另外一个位置才能完成任务。所以呢?这里就有了 寻址的耗时 ,此外还有 旋转耗时 ,读写头需要等待磁盘碟片上的目标数据“旋转到位”才能进行读写操作。使用缓存当然是能够提升性能的,但上述原理仍然适用。在这段(“I/O 等待”)时间内,线程是处于“阻塞”等待状态,也就是说没干啥正事!此时操作系统可以将这个空闲的 CPU 核心用于服务其他线程。这里我们可以总结一下,当你的线程处理的是 I/O 密集型业务时,便可以让线程/连接数设置的比 CPU 核心大一些,这样就能够在同样的时间内,完成更多的工作,提升吞吐量。那么问题又来了?大小设置成多少合适呢?这要取决于 磁盘,如果你使用的是 SSD 固态硬盘,它不需要寻址,也不需要旋转碟片。打住打住!!!你千万可别理所当然的认为:“既然 SSD 速度更快,我们把线程数的大小设置的大些吧!!”结论正好相反!无需寻址和没有旋回耗时的确意味着更少的阻塞,所以更少的线程(更接近于 CPU 核心数)会发挥出更高的性能。只有当阻塞密集时,更多的线程数才能发挥出更好的性能。
网络 IO
上面我们已经说过了磁盘 IO, 接下来我们谈谈网络 IO!网络 IO 其实也是非常相似的。通过以太网接口读写数据时也会造成阻塞,10G 带宽会比 1G 带宽的阻塞耗时少一些,而 1G 带宽又会比 100M 带宽的阻塞少一些。通常情况下,我们把网络 IO 放在第三顺位来考虑,然而有些人会在性能计算中忽略网络 IO 带来的影响。
上图是 PostgreSQL 的基准性能测试数据,从图中我们可以看到,TPS 在连接数达到 50 时开始变缓。回过头来想下,在上面 Oracle 的性能测试视频中,测试人员们将连接数从 2048 降到了 96,实际上 96 还是太高了,除非你的服务器 CPU 核心数有 16 或 32。
连接数计算公式
下面公式由 PostgreSQL 提供,不过底层原理是不变的,它适用于市面上绝大部分数据库产品。还有,你应该模拟预期的访问量,并通过下面的公式先设置一个偏合理的值,然后在实际的测试中,通过微调,来寻找最合适的连接数大小。连接数 = ((核心数 * 2) + 有效磁盘数)
核心数不应包含超线程(hyperthread),即使打开了超线程也是如此,如果热点数据全被缓存了,那么有效磁盘数实际是 0,随着缓存命中率的下降,有效磁盘数也逐渐趋近于实际的磁盘数。另外需要注意,这一公式作用于 SSD 的效果如何,尚未明了。
好了,按照这个公式,如果说你的服务器 CPU 是 4 核 i7 的,连接池大小应该为 ((4*2)+1)=9。
额外需要注意的点
实际上,连接池的大小的设置还是要结合实际的业务场景来说事。比如说,你的系统同时混合了 长事务 和 短事务,这时,根据上面的公式来计算就很难办了。正确的做法应该是创建两个连接池,一个服务于长事务,一个服务于"实时"查询,也就是短事务。还有一种情况,比方说一个系统执行一个任务队列,业务上要求同一时间内只允许执行一定数量的任务,这时,我们就应该让并发任务数去适配连接池连接数,而不是连接数大小去适配并发任务数。
C/C++Linux服务器开发/后台架构师系统学习Linux 服务器开发/架构师面试题、学习资料、教学视频和学习路线图(资料包括 C/C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享有需要的可以自行添加学习群960994558
评论