稳稳的掌握“数据库链接池

摘要

如何打造高性能的数据库链接池框架,能够从哪些角度进行优化,链接池的大量优化实践如何为你的系统保驾护航,本专题将带你走进链接池的世界,为你一一揭晓。java

你们可能会有这样疑问:链接池相似于线程池或者对象池,就是一个放链接的池子,使用的时候从里面拿一个,用完了再归还,功能很是简单,有什么可讲的。数据库

可能还会有这样的疑问:高性能这么高大上,一个小小的链接池,如何跟高大上靠上边的。缓存

本主题将会全面介绍链接池原理,高性能的设计,优化实践,现有链接池的瓶颈及解决方案。同时也会介绍惟品会自研数据库链接池产品(代号:Caelus)性能优化

为何要有链接池

先看一下链接池所处的位置:服务器

应用框架的业务实现通常都会访问数据库,缓存或者HTTP服务。为何要在访问的地方加上一个链接池呢?网络

下面以访问MySQL为例,执行一个SQL命令,若是不使用链接池,须要通过哪些流程。并发

1:TCP创建链接的三次握手框架

2:MySQL认证的三次握手异步

3:真正的SQL执行分布式

4:MySQL的关闭

5:TCP的四次握手关闭

能够看到,为了执行一条SQL,却多了很是多咱们不关心的网络交互。

优势:实现简单。

缺点

1:网络IO较多

2:数据库的负载较高

3:响应时间较长及QPS较低

4:应用频繁的建立链接和关闭链接,致使临时对象较多,GC频繁

5:在关闭链接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL以后关闭)

使用链接池流程

第一次访问的时候,须要创建链接。 可是以后的访问,均会复用以前建立的链接。

优势

1:较少了网络开销

2:系统的性能会有一个实质的提高

3:没了麻烦的TIME_WAIT状态

固然,现实每每是残酷的,当咱们解决了一个问题的时候,同时伴随着另一个问题的产生。

使用链接池面临的最大挑战: 链接池的性能

链接数和线程数性能优化

分库DB部署结构

假设有128个分库:32个服务器,每一个服务器有4个schema。按照128个分库的设计,便会新建128个独立数据库链接池。

数据库链接池的模型

特色

1:128个链接池彻底独立,不一样的schema也对应不一样的链接池

2:先经过拆库,读写等策略选择对应的链接池,再从链接池获取一个链接进行操做

3:操做完后,再将链接归还到对应的链接池中。

优势

结构简单,分散竞争

面临的问题:

1:线程数过多

先看一下新建一个链接池,须要新建的线程数的个数。

链接池 线程数 描述 128个分库须要的线程数
C3P0 4 3个helperThread (pollerThread),1个定时任务AdminTaskTimer(DeadlockDetector) 4*128=512
DBCP 1 负责心跳,最小链接数维持,最大空闲时间和防链接泄露 1*128=128
Druid 2 一个异步建立链接。一个异步关闭链接。 2*128=256

能够看到随着分库的增长,无论选用哪一个链接池,线程的个数均会线性增加。线程数过多将会致使内存占用较大: 默认1个线程会占用1M的空间,若是是512个线程,则会占用1M*512=512M上下文切换开销。

Tips:因为stack和heap申请为虚地址空间,可是一旦使用就不会释放。(线程也不必定会占用1M的空间)

2:链接数过多

数据库的链接资源比较重,而且随着链接的增长,数据库的性能会有明显的降低。DBA通常会限制每一个DB创建链接的个数,好比限制为3K 。假设数据库单台限制3K,32台则容量为3K*32=96K。若是应用最大,最小链接数均为10,则每一个应用总计须要128*10=1.28K个链接。那么数据库理论上支持的应用个数为96K/1.28K= 80 台

3:不能链接复用

同一个物理机下面不一样的schema彻底独立,链接不能复用

优化后的数据库链接池模型

特色:

1:只有一个链接池,全部节点共享线程 (解决了线程数过多的问题)

2:每一个物理机对应一个host, host里面维护多个schema,schema存放链接。

3:同一个host下面的不一样schema 能够进行链接复用(解决链接数过多的问题)

获取链接流程:

1:获取链接须要带上 ip,port和schema信息:好比获取的是host31的schema1

2:先到host31的schema1中获取空闲链接,可是schema1无空闲链接,便会从schema2中获取空闲链接。

3:从schema2中获取的链接执行useschema1,该链接便切换到schema1上面。

4:执行对应的SQL操做,执行完成后,归还链接到schema1的池子里面。

优势:

1:链接复用:有效减小链接数。

2:提高性能:避免频繁的新建链接。新建链接的开销比较大,而使用use schema开销很是小

3:有效减小线程数。按现有方案大概只须要4个线程便可。而优化前须要512个线程

缺点:

1:管理较为复杂

2:不符合JDBC接口规范。DataSource只有简单的getConnection()接口,没有针对获取对应schema的链接的接口。须要继承DataSouce,实现特定接口。

事务语句性能优化

优化前执行事务的模型

从链接池里面获取到链接,默认是自动提交。为了开启事务,须要执行setautocommit=false 操做,而后再执行具体的SQL,归还链接的时候,还须要将链接设置为自动提交(须要执行set autocommit=true) 。能够看到开启事务,须要额外执行两条事务的语句。

优化后执行事务的模型

每一个schema里面全部的链接会按照autocommit进行分组。 分为自动提交(autocommit=true) 和非自动提交(autocommit=false)。获取链接时优先获取相同autocommit的分组里的链接,若是没有可用链接则从另一个分组中获取链接,业务操做执行完后,再归还到对应的分组里面。该种机制避免了开启事务多执行的两条事务语句。

锁性能优化

链接池的通用功能:

链接池主要包含五部分:获取链接,归还链接,定时任务,维护组件及资源池

获取链接:

1:获取超时:若是超过规定时间未获取到链接,则会抛出异常

2:有效性检查:当从资源池里面获取到资源,须要检查该资源的有效性,若是失效,再次获取链接。避免执行业务的时候报错。

3:建立链接:能够同步建立,也能够异步建立。

归还链接:

1:归还链接:好比须要检查最大空闲数,肯定是物理关闭仍是归还到链接池

2:销毁链接: 可同步销毁也可异步销毁

定时任务:

1:空闲检查:主要是检查空闲链接,链接空闲超过必定时间,则会关闭链接。

2:最小链接数控制:通常会设置最小链接数。保证当前系统里面最小的链接数。若是不够,则会新建链接。

组件维护:

1:链接状态控制:空闲,使用,删除等状态控制

2:异常处理:对JDBC访问的异常统一处理,若是异常与链接相关,则会将该链接销毁掉。

3:缓存:避免对SQL重复解析,PrepareStatement机制下,会对SQL解析的对象进行缓存。

4:JDBC封装:对JDBC进行了实现,真正的实现是底层的driver,好比MySQL-connector-java 。

资源池:

1:资源池是存放链接的地方,也是链接池最核心的地方。

2:全部的组件基本上都与资源池进行交互,对链接资源的竞争很是激烈。该处的性能将决定了整个链接池的性能。

3:通常资源池的实现是使用JDK提供的BlockingQueue。那么是否有方案能够进行无锁的设计,来避免竞争。

资源池无锁设计

获取链接大概流程:

1:从ThreadLocal里面获取链接,若是没有空闲链接,则从全局链接池(CopyOnWriteArrayList)中获取。

2:若是全局链接池中没有空闲链接,则会异步新建链接。

3:断定超时时间是否大于阈值,若是小于阈值,则进行自旋。不然进行park休眠。

4:链接创建成功后,会对park的线程进行唤醒

主要从四个方面实现了无锁的设计:ThreadLocal,CopyOnWriteArrayList,异步创建链接及自旋。

ThreadLocal

1:每一个线程均有一个链接队列。该队列是全局队列的引用。

2:获取链接时先从ThreadLocal里面拿链接,若是链接是空闲状态,则使用。不然移除掉,再拿下一个,直到拿不到链接为止。

3:归还链接时,只须要归还到Threadlocal的队列里面,同时设置链接为空闲状态

4:若是使用BlockQueue,获取链接时调用poll,归还链接时调用offer,存在两次锁的竞争。优化后经过CAS避免了两次锁的开销(获取链接时,使用CAS置链接为非空闲状态;归还时,使用CAS置链接为空闲状态)

CopyOnWriteArrayList

1:该队列使用场景是:大量读,少许写的操做,而且存储的数据比较有限。而链接池的场景很是适合采用CopyOnWriteArrayList。

2:在获取链接或者归还链接时,只会经过CAS更改链接的状态,不会对链接池进行添加或者删除的操做。

3:通常链接池链接的个数比较可控,CopyOnWriteArrayList在写操做时会对全部链接进行拷贝,对内存影响不大。

异步创建链接

获取到链接后,判断一下是否有并发正在等待获取链接,若是有,则异步创建链接。避免下一个链接的等待。若是CopyOnWriteArrayList没有空闲链接,则异步创建链接。

自旋

该自旋比较相似于JDK对synchronized的自旋机制。若是发现超时时间大于设定的阈值(好比10微秒),则会进行线程挂起。若是小于设定的阈值,则从新获取链接,进行自选,避免线程的上下文切换带来的性能开销。。

优化小技巧

方法内联优化

1:每调用一次方法,线程便会新建一个栈帧,新建栈帧开销相对比较大

2:JIT在运行时会进行内联优化,多个方法使用一个栈帧,避免栈帧新建过多

3:JIT方法内联优化默认的字节码个数阈值是35个字节,低于35个字节,才会进行优化。(可经过-XX:MaxInlineSize=35进行设置)

经过修改上述代码,编译后字节码修改到34个字节,则能够知足内联的条件。

心跳语句选择

PrepareStatement模式选择

MySQL driver默认是client模式,若是须要开启server模式,须要设置 useServerPrepStmts=true 。PrepareStatement默认的client模式和Statement对于DB端没有区别。你们广泛理解PrepareStatement和Statement的区别是PrepareStatement能够避免SQL注入。可是避免SQL注入是如何作到的?

使用PrepareStatement设置参数的时候,好比调用setString(int parameterIndex, String x),本地会对设置的参数进行转义来避免SQL注入。

执行SQL的时候,会将SQL的?替换成转义后的字符,发送到数据库执行。

PSCache

MySQLdriver 默认不开启,可经过设置 cachePrepStmts = true 进行开启

QueryTimeout

以前也遇到由于开启了queryTimeout,致使链接泄露的问题。

惟品会自研链接池:Caelus

Caelus是惟品会自研的高性能的分布式的数据库链接池。

  • 高性能:基于无锁的链接池设计模型来提高链接池性能;

  • 在分库较多的场景下,减小线程数。 假若有128个分库,现有链接池模型下则须要使用128个独立的链接池,每一个链接池都须要线程(1-4个,不一样的链接池不一样)处理任务。则总共须要维护128到128*4个线程,开销巨大。而Caelus链接池会大大减小线程数。

  • 链接复用。 对于 一个MySQL 的instance上面有多个schema场景下。现有链接池不一样的schema的链接不可复用。而Caelus能够复用不一样schema的链接,提高性能。

  • 过多的事务指令。若是是事务语句,则从链接池拿到链接后,须要先开启事务(setautocommit=false),归还时须要再设置(set autocommit=true)。每使用一次链接,均须要额外执行两条事务指令。Caelus能有效减小事务指令。

  • 配置规范的统一。结合MySQL的设置,提供规范统一,最优的配置。 

相关文章
相关标签/搜索