摘要如何搭建一个高性能的数据库连接池框架,从哪些角度进行优化,连接池的大量优化实践如何为你的系统保驾护航,本文专题将带你走进连接池的世界,为你一一揭秘。你可能会有这样的疑惑:连接池类似于线程池或者对象池。它是一个用于存储连接的池。用的时候拿一个,用完了还回去。功能非常简单。我能说什么?可能还有这样的疑惑:这么小的连接池怎么跟得上这么高的性能。本专题将全面介绍连接池的原理、高性能设计、优化实践、现有连接池的瓶颈及解决方案。同时也会介绍为什么唯品会自研的数据库连接池产品(代号:Caelus)会有连接池。我们先来看一下连接池的位置:应用框架的业务实现一般会访问数据库、缓存或者HTTP服务。为什么要在访问的地方加一个连接池呢?下面以访问MySQL为例,执行一条SQL命令。如果不使用连接池,需要经过哪些流程。1:建立TCP连接的三次握手2:MySQL认证的三次握手3:真正的SQL执行4:MySQL关闭5:TCP四次握手关闭与网络交互有关。优点:实施简单。缺点:1:网络IO较多2:数据库负载高3:响应时间长,QPS低4:应用频繁创建和关闭连接,导致临时对象较多,GC频繁5:关闭连接后,会产生较大TIME_WAITTCP状态的数量(在2个MSL后关闭)当使用连接池进程进行首次访问时,需要建立连接。但是,后续访问将重用先前创建的连接。优点:1:网络开销少2:系统的性能会得到大幅度提升3:没有麻烦的TIME_WAIT状态当然,现实往往是残酷的,当我们解决一个问题的时候,伴随着另一个问题出现。使用连接池最大的挑战:连接池的性能和线程数。性能优化分库DB部署结构:假设有128个分库:32台服务器,每台服务器有4个schema。按照128个分库的设计,会创建128个独立的数据库连接池。数据库连接池的模型特点:1:128个连接池完全独立,不同的schema也对应不同的连接池2:首先通过拆库、读写等策略选择对应的连接池,然后从连接池中获取连接执行操作3:操作完成后,将连接归还给对应的连接池。优点:结构简单,竞争分散问题:1:线程过多首先看新建连接池时需要新建的线程数。连接池线程数描述128个子库C3P043helperThread(pollerThread)、1个定时任务AdminTaskTimer(DeadlockDetector)4*128=512DBCP1负责心跳、最小维护连接数、最大空闲时间和connectionleakageprevention1*128=128Druid2一个异步创建的连接。异步关闭连接。2*128=256可以看出,随着分库的增加,无论选择哪个连接池,线程数都会线性增加。线程太多会导致内存占用大:默认1个线程会占用1M空间,如果有512个线程,会占用1M*512=512M的上下文切换开销。Tips:由于栈和堆申请的是虚拟地址空间,一旦使用就不会释放。(线程不一定占用1M空间)2:连接数过多的数据库连接资源比较重,随着连接数的增加,数据库的性能会明显下降。DBA一般会限制每个DB建立的连接数,比如限制为3K。假设单个数据库限制为3K,那么32个数据库的容量就是3K*32=96K。如果应用***,最小连接数为10,则每个应用一共需要128*10=1.28K个连接。那么数据库理论上支持的应用数是96K/1.28K=803:不能连接复用同一台物理机下的不同schema是完全独立的,连接不能复用优化后的数据库连接池模型特点:1:只能一个连接池,所有节点共享线程(解决线程太多的问题)2:每台物理机对应一个host,在host中维护多个schema,schema存储连接。3:同一主机下的不同schema可以进行连接复用(解决连接过多的问题)。获取连接过程:1:获取连接需要带上ip、端口和schema信息:比如获取host31的schema12:首先从host31的schema1中获取空闲连接,但是如果schema1中没有空闲连接,它将从schema2获得空闲连接。3:从schema2获取的连接执行useschema1,连接切换到schema1。4:执行相应的SQL操作,执行完成后返回到schema1连接的pool中。优点:1:连接复用:有效减少连接数。2:提高性能:避免频繁新建连接。创建新连接的开销比较高,而使用useschema的开销很小。3:有效减少线程数。按照现有的方案,只需要4个线程。优化前需要512个线程。缺点:1:管理比较复杂2:不符合JDBC接口规范。DataSource只有简单的getConnection()接口,没有获取对应schema的连接接口。需要继承DataSouce,实现特定的接口。事务语句性能优化优化前,事务执行模型从连接池中获取连接,默认为自动提交。为了启动事务,需要执行setautocommit=false操作,然后执行具体的SQL。返回连接时,还需要将连接设置为autocommit(需要执行setautocommit=true)。可以看到要启动一个事务,还需要执行两条额外的事务语句。优化后,每个模式中的所有连接将根据自动提交进行分组。分为自动提交(autocommit=true)和非自动提交(autocommit=false)。获取连接时,首先获取同一个自动提交组中的连接。如果没有可用连接,则从另一个组获取连接。业务操作完成后,归还给相应的组。这种机制避免了多次执行打开事务的两个事务语句。锁性能优化连接池的一般功能:连接池主要包括五个部分:获取连接、返回连接、定时任务、维护组件和资源池获取连接:1:获取超时:如果在指定时间内没有获取到连接,会抛出异常2:有效性检查:从资源池中获取资源时,需要检查资源的有效性。如果失败,则重新获得连接。避免执行业务时报错。3:创建连接:可以同步创建,也可以异步创建。Returnconnection:1:返回连接:比如需要检查最大空闲数,判断是物理关闭还是返回连接池2:Destroyconnection:可以同步或者异步销毁定时任务:1:Idlecheck:主要是检查idleConnection,如果连接空闲了一定时间,就会关闭连接。2:最小连接数控制:一般设置最小连接数。保证当前系统的最小连接数。如果没有,将创建一个新连接。组件维护:1:连接状态控制:idle、use、delete等状态控制2:异常处理:JDBC访问异常统一处理,如果异常与连接相关,则连接被销毁。3:缓存:避免SQL的重复解析。在PrepareStatement机制下,SQL解析出来的对象会被缓存起来。4:JDBC封装:实现了JDBC,真正实现的是底层驱动,比如MySQL-connector-java。资源池:1:资源池是存放连接的地方,也是连接池的核心。2:所有组件基本都和资源池交互,连接资源的竞争非常激烈。这里的性能将决定整个连接池的性能。3:一般资源池的实现是使用JDK提供的BlockingQueue。那么有无锁设计的解决方案来避免竞争。资源池无锁设计获取连接的一般流程:1:从ThreadLocal获取连接,如果没有空闲连接,则从全局连接池(CopyOnWriteArrayList)获取。2:如果全局连接池中没有空闲连接,则异步创建一个新连接。3:判断超时时间是否大于阈值,小于阈值则自旋。否则,停车睡觉。4:连接建立成功后,公园的线程会被唤醒,主要从四个方面实现无锁设计:ThreadLocal、CopyOnWriteArrayList、异步连接建立和自旋。ThreadLocal1:每个线程都有一个连接队列。队列是对全局队列的引用。2:获取连接时,先从ThreadLocal获取连接,如果连接空闲就使用。否则,删除它并进行下一个连接,直到连接不再可用。3:返回连接时,只需要返回到Threadlocal队列中,同时将连接置为空闲状态即可。4:如果使用BlockQueue,获取连接时调用poll,返回连接时调用offer。有两个锁比赛。优化后,通过CAS避免了两次锁的开销(获取连接时,使用CAS设置连接为非空闲状态;返回时,使用CAS设置连接为空闲状态)CopyOnWriteArrayList1:使用场景这个队列的特点是:读操作量大,写操作量小,存储的数据比较有限。连接池场景很适合CopyOnWriteArrayList。2:在获取连接或返回连接时,只会通过CAS改变连接的状态,不会进行添加或删除连接池的操作。3:一般情况下连接池的连接数是比较可控的。CopyOnWriteArrayList会在写操作的时候复制所有的连接,对内存影响不大。异步建立连接获取到连接后,判断是否有并发等待获取连接,如果有则异步建立连接。避免等待下一个连接。如果CopyOnWriteArrayList没有空闲连接,则异步建立连接。Spin自旋类似于JDK对synchronized的自旋机制。如果发现超时时间大于设定的阈值(比如10微秒),线程就会被挂起。如果小于设定的阈值,则重新获取连接并自选,避免线程上下文切换带来的性能开销。.优化tips方法内联优化1:线程每次调用都会创建一个新的栈帧,创建新栈帧的成本比较高2:JIT会在运行时进行内联优化,多个方法使用一个栈frametoavoidToomanynewstackframes3:JIT方法内联优化默认的字节码阈值是35字节,只有小于35字节才会进行优化。(可以通过-XX:MaxInlineSize=35设置)通过修改上面的代码,将编译后的字节码修改为34字节,可以满足内联条件。心跳语句选择PrepareStatement方式,选择MySQL驱动。默认是客户端模式。如果需要启用服务器模式,则需要设置useServerPrepStmts=true。DB端默认的PrepareStatement和Statement客户端模式没有区别。一般理解PrepareStatement和Statement的区别在于PrepareStatement可以避免SQL注入。但是如何避免SQL注入呢?使用PrepareStatement设置参数时,比如调用setString(intparameterIndex,Stringx),本地会对设置的参数进行转义,避免SQL注入。执行SQL时,SQL?将被转义字符替换并发送到数据库执行。PSCacheMySQLdriver默认是不开启的,可以通过设置cachePrepStmts=true来开启QueryTimeout,之前你也遇到过开启queryTimeout导致连接泄露的问题。唯品会自研连接池:CaelusCaelus是唯品会自研的一款高性能分布式数据库连接池。高性能:基于无锁连接池设计模型提升连接池性能;在分库较多的场景下减少线程数。如果有128个分库,在现有的连接池模型下需要使用128个独立的连接池,每个连接池需要线程(1-4个,不同的连接池不同)来处理任务。那么总共需要维护128到128*4个线程,这是一个巨大的开销。Caelus连接池会大大减少线程数。连接重用。针对一个MySQL实例上有多个schema的场景。现有连接池中不同模式的连接不能被重用。Caelus可以重用不同模式的连接以提高性能。交易指令过多。如果是事务语句,从连接池中获取连接后,需要启动事务(setautocommit=false),返回时需要重新设置(setautocommit=true)。每次使用连接时,都需要执行两条额外的事务指令。Caelus可以有效减少交易指令。统一配置规范。结合MySQL设置,提供标准化、统一的配置。
