连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用
这种连接“汇集”起来的技术基于这样的一个事实:对于大多数应用程序,当它们正在处理通常需要数毫秒完成的事务时,仅需要能够访问JDBC连接的 1 个线程。当不处理事务时,这个连接就会闲置。相反,连接池允许闲置的连接被其它需要的线程使用。
事实上,当一个线程需要用 JDBC 对一个 GBase 或其它数据库操作时,它从池中请求一个连接。当这个线程使用完了这个连接,将它返回到连接池中,这样这就可以被其它想使用它的线程使用。
当连接从池中“借出”,它被请求它的线程专有地使用。从编程的角度来看,这和用户的线程每当需要一个 JDBC 连接的时候调用DriverManager.getConnection() 是一样的,采用连接池技术,可通过使用新的或已有的连接结束线程。
连接池可以极大的改善用户的 Java 应用程序的性能,同时减少全部资源的使用。连接池主要的优点有:
减少连接创建时间
虽然与其它数据库相比 GBase 提供了较为快速连接功能,但是创建新的 JDBC 连接仍会招致网络和 JDBC 驱动的开销。如果这类连接是“循环”使用的,使用该方式这些花销就可避免。
简化的编程模式
当使用连接池时,每一个单独的线程能够像创建了一个自己的 JDBC 连接一样操作,允许用户直接使用JDBC编程技术。
受控的资源使用
如果用户不使用连接池,而是每当线程需要时创建一个新的连接,那么用户的应用程序的资源使用会产生非常大的浪费并且可能会导致高负载下的异常发生。
注意,每个连到 GBase 的连接在客户端和服务器端都有花销(内存,CPU,上下文切换等等)。每个连接均会对应用程序和 GBase 服务器的可用资源带来一定的限制。不管这些连接是否在做有用的工作,仍将使用这些资源中的相当一部分。
连接池能够使性能最大化,同时还能将资源利用控制在一定的水平之下,如果超过该水平,应用程序将崩溃而不仅仅是变慢。
在实际应用开发中,特别是在WEB应用系统中,如果JSP、Servlet或EJB使用JDBC直接访问数据库中的数据,每一次数据访问请求都必须经历建立数据库连接、打开数据库、存取数据和关闭数据库连接等步骤,而连接并打开数据库是一件既消耗资源又费时的工作,如果频繁发生这种数据库操作,系统的性能必然会急剧下降,甚至会导致系统崩溃。数据库连接池技术是解决这个问题最常用的方法,在许多应用程序服务器(例如:Weblogic,WebSphere,JBoss)中,基本都提供了这项技术,无需自己编程,但是,深入了解这项技术是非常必要的。
数据库连接池技术的思想非常简单,将数据库连接作为对象存储在一个Vector对象中,一旦数据库连接建立后,不同的数据库访问请求就可以共享这些连接,这样,通过复用这些已经建立的数据库连接,可以克服上述缺点,极大地节省系统资源和时间。
数据库连接池的主要操作如下:
(1)建立数据库连接池对象(服务器启动)。
(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。
(4)存取数据库。
(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。
(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。
实现模式编辑
1、连接池模型
本文讨论的连接池包括一个连接池类(DBConnectionPool)和一个连接池管理类(DBConnetionPoolManager)。连接池类是对某一数据库所有连接的“缓冲池”,主要实现以下功能:①从连接池获取或创建可用连接;②使用完毕之后,把连接返还给连接池;③在系统关闭前,断开所有连接并释放连接占用的系统资源;④还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题),并能够限制连接池中的连接总数不低于某个预定值和不超过某个预定值。
连接池管理类是连接池类的外覆类(wrapper),符合单例模式,即系统中只能有一个连接池管理类的实例。其主要用于对多个连接池对象的管理,具有以下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,创建连接池对象;③为方便管理多个连接池对象,为每一个连接池对象取一个名字,实现连接池名字与其实例之间的映射;④跟踪客户使用连接情况,以便需要时关闭连接释放资源。连接池管理类的引入主要是为了方便对多个连接池的使用和管理,如系统需要连接不同的数据库,或连接相同的数据库但由于安全性问题,需要不同的用户使用不同的名称和密码。
2、连接池实现
下面给出连接池类和连接池管理类的主要属性及所要实现的基本接口:
public class DBConnectionPool implements TimerListener{
private int checkedOut;//已被分配出去的连接数
private ArrayList freeConnections = new ArrayList();//容器,空闲池,根据//创建时间顺序存放已创建但尚未分配出去的连接
private int minConn;//连接池里连接的最小数量
private int maxConn;//连接池里允许存在的最大连接数
private String name;//为这个连接池取个名字,方便管理
private String password;//连接数据库时需要的密码
private String url;//所要创建连接的数据库的地址
private String user;//连接数据库时需要的用户名
public Timer timer;//定时器
public DBConnectionPool(String name, String URL, String user, String
password, int maxConn)//公开的构造函数
public synchronized void freeConnection(Connection con) //使用完毕之后,//把连接返还给空闲池
public synchronized Connection getConnection(long timeout)//得到一个连接,//timeout是等待时间
public synchronized void release()//断开所有连接,释放占用的系统资源
private Connection newConnection()//新建一个数据库连接
public synchronized void TimerEvent() //定时器事件处理函数
}
public class DBConnectionManager {
static private DBConnectionManager instance;//连接池管理类的唯一实例
static private int clients;//客户数量
private ArrayList drivers = new ArrayList();//容器,存放数据库驱动程序
private HashMap pools = new HashMap ();//以name/value的形式存取连接池//对象的名字及连接池对象
static synchronized public DBConnectionManager getInstance()//如果唯一的//实例instance已经创建,直接返回这个实例;否则,调用私有构造函数,创//建连接池管理类的唯一实例
private DBConnectionManager()//私有构造函数,在其中调用初始化函数init()
public void freeConnection(String name, Connection con)// 释放一个连接,//name是一个连接池对象的名字
public Connection getConnection(Stringname)//从名字为name的连接池对象//中得到一个连接
public Connection getConnection(Stringname, long time)//从名字为name
//的连接池对象中取得一个连接,time是等待时间
public synchronized void release()//释放所有资源
private void createPools(Properties props)//根据属性文件提供的信息,创建//一个或多个连接池
private void init()//初始化连接池管理类的唯一实例,由私有构造函数调用
private void loadDrivers(Properties props)//装载数据库驱动程序
}
3、连接池使用
上面所实现的连接池在程序开发时如何应用到系统中呢?下面以Servlet为例说明连接池的使用。
Servlet的生命周期是:在开始建立servlet时,调用其初始化(init)方法。之后每个用户请求都导致一个调用前面建立的实例的service方法的线程。最后,当服务器决定卸载一个servlet时,它首先调用该servlet的 destroy方法。
根据servlet的特点,我们可以在初始化函数中生成连接池管理类的唯一实例(其中包括创建一个或多个连接池)。如:
public void init() throws ServletException
{
connMgr = DBConnectionManager.getInstance();
}
然后就可以在service方法中通过连接池名称使用连接池,执行数据库操作。最后在destroy方法中释放占用的系统资源,如:
public void destroy() {
connMgr.release(); super.destroy();
}
什么是连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
为什么要使用连接池
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。 连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
传统的连接机制与数据库连接池的运行机制区别
不使用连接池流程
下面以访问MySQL为例,执行一个SQL命令,如果不使用连接池,需要经过哪些流程。
不使用数据库连接池的步骤:
TCP建立连接的三次握手
MySQL认证的三次握手
真正的SQL执行
MySQL的关闭
TCP的四次握手关闭
可以看到,为了执行一条SQL,却多了非常多我们不关心的网络交互。
优点:
实现简单
缺点:
网络IO较多
数据库的负载较高
响应时间较长及QPS较低
应用频繁的创建连接和关闭连接,导致临时对象较多,GC频繁
在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)
使用连接池流程
使用数据库连接池的步骤:
第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行SQL语句。
优点:
较少了网络开销
系统的性能会有一个实质的提升
没了麻烦的TIME_WAIT状态
数据库连接池的工作原理
连接池的工作原理主要由三部分组成,分别为
连接池的建立
连接池中连接的使用管理
连接池的关闭
第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。
第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。
连接池主要参数
使用连接池时,要配置一下参数
最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.
最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
最大空闲时间
获取连接超时时间
超时重试连接次数
连接池需要注意的点
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)lock(C#)关键字即可确保线程是同步的。使用方法可以参考,相关文献。
2、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
3、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他(如何能找到最合适的连接文章将在关键议题中指出);如果没有就抛出一个异常给用户,List中连接是否可以被分配由一个线程来专门管理捎后我会介绍这个线程的具体实现。
4、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
数据库对比
第一、二代连接池
区分一个数据库连接池是属于第一代产品还是代二代产品有一个最重要的特征就是看它在架构和设计时采用的线程模型,因为这直接影响的是并发环境下存取数据库连接的性能。
一般来讲采用单线程同步的架构设计都属于第一代连接池,二采用多线程异步架构的则属于第二代。比较有代表性的就是Apache Commons DBCP,在1.x版本中,一直延续着单线程设计模式,到2.x才采用多线程模型。
用版本发布时间来辨别区分两代产品,则一个偷懒的好方法。以下是这些常见数据库连接池最新版本的发布时间:
数据库连接池
最新版本
发布时间
C3P0
c3p0-0.9.5.2
on 9 Dec 2015
DBCP
2.2.0
27 December 2017
Druid
0.11.0
Dec 4 2017
HikariCP
2.7.6
2018-01-14
从表中可以看出,C3P0已经很久没有更新了。DBCP更新速度很慢,基本处于不活跃状态,而Druid和HikariCP处于活跃状态的更新中,这就是我们说的二代产品了。
二代产品对一代产品的超越是颠覆性的,除了一些“历史原因”,你很难再找到第二条理由说服自己不选择二代产品,但任何成功都不是偶然的,二代产品的成功很大程度上得益于前代产品们打下的基础,站在巨人的肩膀上,新一代的连接池的设计师们将这一项“工具化”的产品,推向了极致。其中,最具代表性的两款产品是:
HikariCP
Druid
彻底死掉的C3P0
C3P0是我使用的第一款数据库连接池,在很长一段时间内,它一直是Java领域内数据库连接池的代名词,当年盛极一时的Hibernate都将其作为内置的数据库连接池,可以业内对它的稳定性还是认可的。C3P0功能简单易用,稳定性好这是它的优点,但是性能上的缺点却让它彻底被打入冷宫。C3P0的性能很差,差到即便是同时代的产品相比它也是垫底的,更不用和Druid、HikariCP等相比了。正常来讲,有问题很正常,改就是了,但c3p0最致命的问题就是架构设计过于复杂,让重构变成了一项不可能完成的任务。随着国内互联网大潮的涌起,性能有硬伤的c3p0彻底的退出了历史舞台。
咸鱼翻身的DBCP
DBCP(DataBase Connection Pool)属于Apache顶级项目Commons中的核心子项目(最早在Jakarta Commons里就有),在Apache的生态圈中的影响里十分广泛,比如最为大家所熟知的Tomcat就在内部集成了DBCP,实现JPA规范的OpenJPA,也是默认集成DBCP的。但DBCP并不是独立实现连接池功能的,它内部依赖于Commons中的另一个子项目Pool,连接池最核心的“池”,就是由Pool组件提供的,因此,DBCP的性能实际上就是Pool的性能,DBCP和Pool的依赖关系如下表:
Apache Commons DBCP
Apache Commons Pool
v1.2.2
v1.3
v1.3
v1.5.4
v1.4
v1.5.4
v2.0.x
v2.2
v2.1.x
v2.4.2
v2.2.x
v2.5.0
可以看到,因为核心功能依赖于Pool,所以DBCP本身只能做小版本的更新,真正大版本的更迭则完全依托于pool。有很长一段时间,pool都还是停留在1.x版本,这直接导致DBCP也更新乏力。很多依赖DBCP的应用在遇到性能瓶颈之后,别无选择,只能将其替换掉,DBCP忠实的拥趸tomcat就在其tomcat 7.0版本中,自己重新设计开发出了一套连接池(Tomcat JDBC Pool)。好在,在2013年事情终于迎来转机,13年9月Commons-Pool 2.0版本发布,14年2月份,DBCP也终于迎来了自己的2.0版本,基于新的线程模型全新设计的“池”让DBCP重焕青春,虽然和新一代的连接池相比仍有一定差距,但差距并不大,DBCP2.x版本已经稳稳达到了和新一代产品同级别的性能指标(见下图)。
DBCP终于靠Pool咸鱼翻身,打了一个漂亮的翻身仗,但长时间的等待已经完全消磨了用户的耐心,与新一代的产品项目相比,DBCP没有任何优势,试问,谁会在有选择的前提下,去选择那个并不优秀的呢?也许,现在还选择DBCP2的唯一理由,就是情怀吧。
性能无敌的HikariCP
HikariCP号称“性能杀手”(It’s Faster),它的表现究竟如何呢,先来看下官网提供的数据:
不光性能强劲,稳定性也不差:
那它是怎么做到如此强劲的呢?官网给出的说明如下:
字节码精简:优化代码,直到编译后的字节码最少,这样,CPU缓存可以加载更多的程序代码;
优化代理和拦截器:减少代码,例如HikariCP的Statement proxy只有100行代码;
自定义数组类型(FastStatementList)代替ArrayList:避免每次get()调用都要进行range check,避免调用remove()时的从头到尾的扫描;
自定义集合类型(ConcurrentBag):提高并发读写的效率;
其他缺陷的优化,比如对于耗时超过一个CPU时间片的方法调用的研究(但没说具体怎么优化)。
可以看到,上述这几点优化,和现在能找到的资料来看,HakariCP在性能上的优势应该是得到共识的,再加上它自身小巧的身形,在当前的“云时代、微服务”的背景下,HakariCP一定会得到更多人的青睐。
功能全面的Druid
近几年,阿里在开源项目上动作频频,除了有像fastJson、dubbo这类项目,更有像AliSQL这类的大型软件,今天说的Druid,就是阿里众多优秀开源项目中的一个。它除了提供性能卓越的连接池功能外,还集成了SQL监控,黑名单拦截等功能,用它自己的话说,Druid是“为监控而生”。借助于阿里这个平台的号召力,产品一经发布就赢得了大批用户的拥趸,从用户使用的反馈来看,Druid也确实没让用户失望。
相较于其他产品,Druid另一个比较大的优势,就是中文文档比较全面(毕竟是国人的项目么),在github的wiki页面,列举了日常使用中可能遇到的问题,对一个新用户来讲,上面提供的内容已经足够指导它完成产品的配置和使用了。
下图为Druid自己提供的性能测试数据:
现在项目开发中,我还是比较倾向于使用Durid,它不仅仅是一个数据库连接池,它还包含一个ProxyDriver,一系列内置的JDBC组件库,一个SQL Parser。
Druid 相对于其他数据库连接池的优点
强大的监控特性,通过Druid提供的监控功能,可以清楚知道连接池和SQL的工作情况。
a. 监控SQL的执行时间、ResultSet持有时间、返回行数、更新行数、错误次数、错误堆栈信息;
b. SQL执行的耗时区间分布。什么是耗时区间分布呢?比如说,某个SQL执行了1000次,其中0~1毫秒区间50次,1~10毫秒800次,10~100毫秒100次,100~1000毫秒30次,1~10秒15次,10秒以上5次。通过耗时区间分布,能够非常清楚知道SQL的执行耗时情况;
c. 监控连接池的物理连接创建和销毁次数、逻辑连接的申请和关闭次数、非空等待次数、PSCache命中率等。
方便扩展。Druid提供了Filter-Chain模式的扩展API,可以自己编写Filter拦截JDBC中的任何方法,可以在上面做任何事情,比如说性能监控、SQL审计、用户名密码加密、日志等等。
Druid集合了开源和商业数据库连接池的优秀特性,并结合阿里巴巴大规模苛刻生产环境的使用经验进行优化。
总结
时至今日,虽然每个应用(需要RDBMS的)都离不开连接池,但在实际使用的时候,连接池已经可以做到“隐形”了。也就是说在通常情况下,连接池完成项目初始化配置之后,就再不需要再做任何改动了。不论你是选择Druid或是HikariCP,甚至是DBCP,它们都足够稳定且高效!之前讨论了很多关于连接池的性能的问题,但这些性能上的差异,是相较于其他连接池而言的,对整个系统应用来说,第二代连接池在使用过程中体会到的差别是微乎其微的,基本上不存在因为连接池的自身的配饰和使用导致系统性能下降的情况,除非是在单点应用的数据库负载足够高的时候(压力测试的时候),但即便是如此,通用的优化的方式也是单点改集群,而不是在单点的连接池上死扣。
https://www.w3cschool.cn/architectroad/architectroad-connection.html
什么是连接池
1)连接池,首先从字面意思理解,这是一个偏正短语,左偏右正,重点在池这个字上。 池(Pool)技术在一定程度上可以明显优化服务器应用程序的性能,提高程序执行效率和降低系统资源开销。这里所说的池是一种广义上的池,比如数据库连接池、线程池、内存池、对象池等。其中,对象池可以看成保存对象的容器,在进程初始化时创建一定数量的对象。需要时直接从池中取出一个空闲对象,用完后并不直接释放掉对象,而是再放到对象池中以方便下一次对象请求可以直接复用。其他几种池的设计思想也是如此,池技术的优势是,可以消除对象创建所带来的延迟,从而提高系统的性能。
2)数据库连接”是一种稀缺的资源,建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,不是关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。
这就好比是要从A城市去B城市,如果提前修了一条公路(类似我们这块连接池里的连接这个资源)我们要去就可以直接开车去了,若果没有公路,我们还得现修公路,这将非常耗时耗力。向下图中一样,我们连接数据库的时候先到连接池去找连接资源,如果有我们就直接使用,如果没有或者不够我们才创建一个连接。
二、知识剖析
2.1连接池关键问题剖分析:
1)并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为Java语言自身提供了对并发管理的支持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:
public synchronized Connection getConnection()
2)多数据库服务器和多用户
对于大型的企业级应用,常常需要同时连接不同的数据库(如连接Oracle和Sybase)。如何连接不同的数据库呢?我们采用的策略是:设计一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件,其中资源文件中存放着多个数据库的url地址()?用户名()?密码()等信息。如tx.url=172.21.15.123:5000/tx_it,tx.user=yang,tx.password=yang321。根据资源文件提供的信息,创建多个连接池类的实例,每一个实例都是一个特定数据库的连接池。连接池管理类实例为每个连接池实例取一个名字,通过不同的名字来管理不同的连接池。
对于同一个数据库有多个用户使用不同的名称和密码访问的情况,也可以通过资源文件处理,即在资源文件中设置多个具有相同url地址,但具有不同用户名和密码的数据库连接信息。
3)事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
在Java语言中,Connection类本身提供了对事务的支持,可以通过设置Connection的AutoCommit属性为false,然后显式的调用commit或rollback方法来实现。但要高效的进行Connection复用,就必须提供相应的事务支持机制。可采用每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。
4)连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
5)对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给他(实际是先做连接是否有效的判断,如果可用就分配给用户,如不可用就把这个连接从空闲池删掉,重新检测空闲池是否还有连接);如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxConn),如果没有达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果等待时间超过预定时间timeout,则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池。对于空闲连接的状态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
2.2连接池的实现:
1)连接池模型——连接池类
本文讨论的连接池包括一个连接池类(DBConnectionPool)和一个连接池管理类(DBConnetionPoolManager)。连接池类是对某一数据库所有连接的“缓冲池”,主要实现以下功能:①从连接池获取或创建可用连接;②使用完毕之后,把连接返还给连接池;③在系统关闭前,断开所有连接并释放连接占用的系统资源;④还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题),并能够限制连接池中的连接总数不低于某个预定值和不超过某个预定值。
2)连接池模型——连接池管理类
连接池管理类是连接池类的外覆类(wrapper),符合单例模式,即系统中只能有一个连接池管理类的实例。其主要用于对多个连接池对象的管理,具有以下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,创建连接池对象;③为方便管理多个连接池对象,为每一个连接池对象取一个名字,实现连接池名字与其实例之间的映射;④跟踪客户使用连接情况,以便需要是关闭连接释放资源。连接池管理类的引入主要是为了方便对多个连接池的使用和管理,如系统需要连接不同的数据库,或连接相同的数据库但由于安全性问题,需要不同的用户使用不同的名称和密码。
2.3数据库的连接
1)加载数据库的驱动
2)通过JCBC建立数据库连接
3)访问数据库,执行sql语句
4)断开数据库连接,释放资源
2.4实现连接池的步骤
1)编写class用作连接池
2)在class构造器里一次性创建若干个连接,将连接保存造LinkedList中
3)实现getConnection从LinkList中返回一个连接
4)操作完后,提供将连接放回连接池中方法。
2.5连接池测试
1)从刚刚写的连接池里面获取连接
2)进行数据库操作
3)操作完成后,把连接返回给连接池
4)具体看编码实战
三、常见问题
1)无法获取数据库连接
2)运行一会报错了
四、解决方案解决方案
4.1无法获取数据库连接
a)无法获取数据库连接的原因,最简单的就是没添加依赖
b) 其次是驱动用户名密码等写错了
c) 也有可能没启动数据库服务或者没开放连接权限
d) 还有一种情况就是,读取配置文件的时候出错,导致连接池初始化失败
4.2运行一会儿报错了
a)报错,可能是因为断网了,一直连不上数据库,所以报错了
b)或者是最大连接数用完了,还在请求创建连接
五、编码实战
这是连接池池的程序
package com.jnshu.datasource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class MyDataSource {
//LinkedList集合,存储连接的容器—连接池
private LinkedList
//初始化连接数量
public MyDataSource() {
//一次性创建10个连接
for(int i = 0; i < 10; i++) {
try {
//1、装载驱动对象
Class.forName("com.mysql.jdbc.Driver");
//2、通过JDBC建立数据库连接
Connection con =DriverManager.getConnection(
"jdbc:mysql://localhost:3306/practice?useSSL=false", "root", "123456");
//3、将连接加入连接池中
dataSources.add(con);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Connection getConnection() throws SQLException {
//取出连接池中一个连接
final Connection conn = dataSources.removeFirst(); // 获取第一个连接给Connection,把剩下的连接返回给LinkedList集合
System.out.println("执行数据库的操作时,连接池内剩余连接个数: "+dataSources.size());//输出集合里面剩下的连接的个数
return conn;
}
public void releaseConnection(Connection conn) {
//把连接返还给连接池
dataSources.add(conn);
System.out.println("数据库操作完成时,连接池剩余连接个数:"+dataSources.size());//输出集合里面剩下的连接的个数
} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 这是测试程序
package com.jnshu.datasource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class DataSourceDemo {
public static void main(String[] args) throws Exception{
//1、使用连接池建立数据库连接
MyDataSource myDataSource = new MyDataSource();
Connection conn =myDataSource.getConnection();
//2、创建状态
Statement state =conn.createStatement();
//3、查询数据库并返回结果
ResultSet result =state.executeQuery(“select * from user where id=5”);
//4、输出查询结果
while(result.next()){
System.out.println(result.getString(“name”));
}
//5、断开数据库连接
result.close();
state.close();
// Connection connection=myDataSource.getConnection();
//6、归还数据库连接给连接池
myDataSource.releaseConnection(conn);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
六、扩展思考
1 连接池有几种?
这个博客对几个常用的连接池的性能分析。https://www.cnblogs.com/linjian/p/4831088.html
DBCP 、C3P0、BoneCP、Proxool、DDConnectionBroker、DBPool、XAPool、Primrose、SmartPool、MiniConnectionPoolManager及Druid等。
2 连接池有什么好处
连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。
共享资源,连接池的使用解决了资源频繁分配、释放所造成的问题的。提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。
3 如何选取连接池
简单的说需要根据所要处理的业务量进行测试后选择合适的连接池,这牵扯到了技术选型。
七、参考文献
https://www.jianshu.com/p/beceba363798
https://www.cnblogs.com/linjian/p/4831088.html
https://www.zhihu.com/question/53589525/answer/275249556
八、更多讨论
1 、连接池最多存连接的个数如何设置
2、使用连接池的优缺点
3、哪种连接池更好用
https://zhuanlan.zhihu.com/p/47480504
Java 2 Enterprise Edition (J2EE) 规范提供了实现高度可伸缩、可靠和可用的电子商务应用的分布式基于服务的体系结构。通常,J2EE 应用体系结构与模型-视图-控制器 (MVC) 框架相对应 – 资源库/外部系统资源支持域模型(模型),JSP/Servlet 管理显示(视图),而 EJB 处理商业逻辑(控制器)。
通过服务器端所有三层中的组件实现一个典型的电子商务应用用例。考虑到用户交互数量的庞大(对于面对客户的应用,有上百万个),需要优化地共享有限的服务器端资源。这类资源可能包括数据库、消息队列、目录、企业系统 (SAP、CICS) 等等,它们中的每一个都可以由使用代表资源访问点的连接对象的应用来访问。管理对那些共享资源的访问对于满足 J2EE 应用的高性能需求来说至关重要。
连接合用是由数据库供应商倡导的技术,其目的是允许客户机共享一组高速缓存的连接对象,这些对象提供对数据库资源的访问。在本文中,我分析了 J2EE 环境中服务器端资源(例如数据库、消息队列、目录和企业系统)的连接合用。
为何合用资源连接?
考虑一下 代码示例 ,其中,EJB 使用 JDBC 1.0、 不使用连接合用来访问数据库资源。
很明显,该示例的主要问题是连接的打开和关闭。考虑到实体 bean 是共享组件,因此,对每个客户机请求,都要进行几次获取和释放数据库连接的操作。
从图 1 可以看出,使用 JDBC 1.0 通过数据库管理器获取和释放数据库连接将影响 EJB 层的性能。这种影响是由数据库资源管理器进程创建和摧毁那些对象而引起的。应用服务器一般需要花 1 到 3 秒的时间来建立数据库连接(包括与服务器通信、认证等等),并需要对每一个客户机 (EJB) 的请求进行连接。
图 1. 使用 JDBC 1.0 的连接管理
使用 JDBC 1.0 的连接管理
使用服务供应商设施的连接合用
现在看一下在 J2EE 环境中,数据库和非数据库资源类型当前可以使用哪些连接合用设施。
JDBC 2.0 标准扩展 API
JDBC 2.0 标准扩展 API 指定数据库服务供应商可以实现具有以下特性的合用技术:允许请求客户机透明地共享资源池的多个连接对象。在那种情况下,因为池管理器预先在启动时创建连接对象,所以,J2EE 组件可以使用连接对象,而不会导致数据库资源管理器上的系统开销。应用服务器供应商在其内存空间实现池管理器,并根据需要动态改变池的大小,从而优化资源的使用。图 2 中显示了这种情况。
图 2. 使用 JDBC 2.0 标准扩展的连接合用
使用 JDBC 2.0 标准扩展的连接合用
通过使用 DataSource 接口 (JDBC 2.0) 或 DriverManager (JDBC 1.0) 接口,J2EE 组件可以获得物理数据库连接对象。要获得逻辑(合用的)连接,J2EE 组件必须使用以下这些 JDBC 2.0 合用管理器接口:
javax.sql.ConnectionPoolDataSource 接口,该接口充当合用的 java.sql.Connection 对象的资源管理器连接 factory。每家数据库服务器供应商都提供该接口的实现(例如,Oracle 实现 oracle.jdbc.pool.OracleConnectionPoolDataSource 类)。
javax.sql.PooledConnection 接口,该接口封装到数据库的物理连接。同样,数据库供应商提供其实现。
对于那些接口和 XA 连接的每一个,都存在一个 XA(X/Open 规范)等价定义。
以下代码示例显示了 EJB 应用如何利用合用的连接对象来访问数据库资源(基于 JDBC 2.0)。本例中的 EJB 组件使用 JNDI 查询来确定数据库连接池资源的位置。JNDI 1.2 标准扩展 API 允许 Java 应用以相同的方式访问位于完全不同的目录和命名系统中的对象。使用 JNDI API,应用可以查询目录来确定任何资源(例如,数据库服务器、LDAP 服务器、打印服务器、消息服务器、文件服务器等等)的位置。有关 JNDI 的合适概述,请参阅 “The Java Naming and Directory Interface (JNDI): A More Open and Flexible Model”。
请注意: 实际代码可能会根据数据库供应商实现类的不同而不同。
以上代码(使用 JDBC 2.0)和使用 JDBC 1.0 的主要不同在于: getConnection() 从池中获取已打开的连接,而 close() 只将连接对象释放回池。如今,几乎每一家数据库服务器供应商(如 Oracle、DB2、Sybase 和 Informix)都提供 JDBC 2.0 驱动程序。如今大多数应用服务器供应商(IBM、BEA、iPlanet、IONA 等)也都支持 JDBC 2.0。
应该说明的一点是:如今,几乎所有应用服务器都采用两层连接合用体系结构,其中,池位于应用服务器内存空间(与独立的连接代理不同)。
JMS 1.02 标准扩展 API
J2EE 应用组件可以使用消息传递资源与其它企业应用异步通信。JMS 1.02 标准扩展 API 提供独立于供应商的方式来与消息传递服务供应商通信。与数据库资源一样,通过使用可以合用的连接对象来访问消息队列。
JMS 1.02 API 包括下列接口以支持资源合用:
用于 factory 对象的 javax.jms.QueueConnectionFactory 或 javax.jms.TopicConnectionFactory
用于连接对象的 javax.jms.QueueConnection 或 javax.jms.TopicConnection
JMS 服务供应商实现那些接口。 代码样本 显示了 EJB 组件如何使用连接对象来访问消息队列资源。
在连接合用时,JMS factory 类通常要有代理(由管理员配置),以便 open() 和 close() 请求实际上发往管理连接池的代理。遵循 JMS API 的指示,JMS 服务器供应商可以实现数据库来管理消息队列。在那种情况下,适当的 JDBC 驱动程序将提供连接合用。如果应用已经使用 JDBC 2.0 连接池启用的数据库,那么,您所要做的只是为 JMS 配置 JNDI 特性,以使用那个 JDBC 实例。
JNDI API for LDAP
javax.naming.LDAP 包包括特定于 LDAP 的类(而不包括在通用 javax.naming.directory 中)。与 JDBC 2.0 和 JMS 1.02 API 不同,JNDI LDAP API 不为连接合用指定任何接口。目录服务供应商可以有选择地通过 SDK 提供支持。例如,iPlanet 的 Netscape Directory Server SDK 4.0 for Java 包括以下构建 LDAP 客户机所用的类:
1
2
public class netscape.ldap.util.ConnectionPool extends java.lang.Object
methods: Connection(), getConnection(), close(), etc.
有关详细信息,请参阅 “Netscape Directory Server Application Programmer’s Guide”。
J2EE Connector Architecture 1.0
在以上所有示例中,EJB 组件必须导入特定于供应商的实现类,以使用资源的连接合用设施。很明显,这种做法降低了 EJB 的可移植性,并不利于 J2EE 的发展。
理想的做法是内置一个可用于任何资源类型和所有连接管理功能(包括合用)的通用连接接口。这就是即将出现的 J2EE Connector Architecture 1.0 规范的目标之一,在我写这篇文章之时,就已经公开了一份草案副本。(请参阅 参考资料)。
图 3 显示了体系结构内部的主要概念, 资源适配器 。应用服务器所支持的每一种资源类型的可插入组件,资源适配器,都在应用服务器地址空间中执行。访问那些适配器的客户机 API 可以是 Common Client Interface (CCI) 或(为了向后兼容)特定于资源的 API(例如 JDBC 2.0)。例如,CCI 定义 javax.resource.cci.ConnectionFactory 和 javax.resource.cci.Connection ,分别作为连接 factory 和连接的接口 – 与上一节中提到的 JDBC 2.0 接口类似。
图 3. J2EE Connector Architecture 1.0 中的资源适配器
J2EE Connector Architecture 1.0 中的资源适配器
Connector 1.0 中的连接合用
Connector 1.0 的编程模型如下:
EJB 执行连接 factory 的 JNDI 查询,然后发出 getConnection() 请求。
连接 factory 将请求委托给 ConnectionManager 。
连接管理器在应用服务器中查询连接池的实例。如果没有可用的连接池,则管理器使用 ManagedConnectionFactory 来创建一个物理(不合用的)连接。
在那种情况下,假定资源适配器供应商实现接口。然而,连接器体系结构并不指定应用服务器如何实现连接池,而是提供一些指示,例如,根据适配器类型、服务质量 (QoS) 需求等来划分连接池。有关详细信息,请参阅 J2EE Connector Architecture 规范。
例如,基于即将出现的 EJB 2.0 连接器体系结构的、至企业/旧有系统的 Sun 连接器的产品版 iPlanet Unified Integration Framework Toolkit v 6.0,为 EJB 层可能要访问的每个后端系统定义了连接池。一个定期执行的线程监控池对象的使用和寿命。有关详细信息,请参阅 iPlanet Unified Integration Framewor。
EJB 层的设计考虑事项
尽管有了管理连接池的资源管理器,但是还不能保证 EJB 层具有最优性能 – 还有一些设计考虑事项!
首先,考虑以下 EJB 客户机代码示例,该客户机访问实现连接池的 LDAP 目录 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import netscape.ldap.util.*;
…
public class NewCustomerBean implements SessionBean {
…
private SessionContext context; // Bean Context
private LDAPConnection lc; // LDAP Connection object
…
public void setSessionContext(SessionContext sc) {
this.context = sc;
// initialize JNDI lookup parameters
Context ctx = new InitialContext(parms);
…
ConnectionPool cp = (ConnectionPool)ctx.lookup(cpsource);
// Establish LDAP Connection.
try {
this.lc = cp.getConnection();
…
}
以上做法有什么不妥吗?首先,有状态会话对象 ( NewCustomerBean ) 在 setEntityContext 中打开连接对象,然后持续占用它,直到使用完为止 – 如果用户(会话)数量迅速增加,就成为代价相当大的实现。第二,也是更重要的,因为连接对象不是序列化的,所以,按照 EJB 1.2 规范,容器可以在钝化时(例如,将会话 bean 从其活动状态移至 bean 实例池)废弃 bean 实例。
一种替代方法是分别在会话 bean 的 ejbActivate() 和 ejbPassivate() 方法中获取和释放资源连接。如果没有连接池,代价当然会很高,也不会建议那样做。然而,有了合用之后,使用该技术,可以用最小的 EJB 层开销来获取和释放连接。这里的要点在于:除了规范和实现所提供的设施之外,设计选择总是关键性能决定因素。
第二个考虑事项是有关认证问题的。您可能已经注意到,合用的连接意味着共享的连接,而共享的连接意味着连接不与特定的认证证书绑定。例如,在 JDBC 2.0 连接中,应用服务器池管理器在启动时,使用一个存储在配置文件中的认证证书(通常是用户标识/口令)来从数据库管理器请求预设数量的连接。有时候,那可能不满足应用的安全性策略。LDAP 连接需要将 LDAP 子树与特定证书绑定,因而也有同样的问题。在那些情况下,一种替代方法可能是使用利用特定证书建立好的已高速缓存的连接,它可以对相同类型的证书重复使用。这种方法的不利之处是已高速缓存的连接要保留很长时间。另一种替代方法可能是对资源使用通用连接,并实现某种应用层安全性。
一.数据库连接池:
1. 预先向数据库申请多个可用的数据库连接,保存到集合对象中,应用程序每次从集合对象中取出可用的数据库连接,执行数据库操作;操作完成后将连接放回集合对象。实现了数据库连接的复用。
2.数据库连接的建立是整个数据库操作中最耗时的操作,我们只做一次数据库操作就开关一次数据库连接,这样是很影响效率的
3.我们是不是可以这样,每次从某一个缓存的地方获取一个数据库连接,使用完之后再归还到那个缓存的地方,不真正意义上的关闭连接
二.编写连接池需实现java.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection方法:
1.Connection getConnection()
2.Connection getConnection(String username, String password)
三.实现DataSource接口,并实现连接池功能的步骤:
1.在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中。
实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户。
当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不要把conn还给数据库。Collection保证将自己返回到LinkedList中是此处编程的难点。
数据库连接池实例:
复制代码
1 public class SimpleDataSource {
2 private static LinkedList
3
4 public static void main(String[] args){
5 new SimpleDataSource();
6
7 }
8
9 public SimpleDataSource(){
10 try{
11 Class.forName("com.mysql.jdbc.Driver");
12 connPool=new LinkedList
13 for(int i=0;i<10;i++){
14 Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test?" ,"h3","111111");
15 connPool.add(conn);
16 System.out.println(conn);
17 }
18 }catch(Exception e){
19 e.printStackTrace();
20 }
21 }
22 public Connection getconn(){//从linkedlist 返回一个connection对象
23 return connPool.removeLast();
24 }
25
26 public void closeConn(Connection conn){//"关闭"数据库连接,不是正真意义上的关闭
27 connPool.add(conn);
28 }
29
30 public void printPoolSize(){
31 System.out.println("连接池size:"+connPool.size());
32 }
33
34 public void releasePool()throws Exception{//关闭每一个链接;关闭数据库连接,真正的关闭
35 Iterator it=connPool.iterator();
36 Connection conn=null;
37 while(it.hasNext()){}
38 conn=(Connection)it.next();
39 conn.close();
40 }
41 }
复制代码
测试数据库连接池:
复制代码
1 public class TestPool {
2 public static void main(String[] args){
3 try{
4 SimpleDataSource sds=new SimpleDataSource();
5 // sds.releasePool();//关闭数据库连接
6
7 Connection conn=sds.getconn();
8 System.out.println(“connection used……….”+conn);
9 sds.printPoolSize();
10
11 queryEmp(conn);
12 sds.closeConn(conn);
13 sds.printPoolSize();
14 System.out.println(“”);
15 }catch(Exception e){
16 e.printStackTrace();
17 }
18 }
19
20 public static void queryEmp(Connection conn){
21 try{
22 String sql=”select * from employees”;
23 Statement stmt=conn.createStatement();
24 ResultSet rs=stmt.executeQuery(sql);
25 while(rs.next()){
26 System.out.print(“工号:”+rs.getInt(1));
27 System.out.println(“工号:”+rs.getString(“last_name”));
28 }
29 rs.close();
30 }catch(Exception e){
31 e.printStackTrace();
32 }
33 }
34 }
复制代码
DBCP数据库连接池:
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName(“org.gjt.mm.mysql.Driver”);
bds.setUrl(“jdbc:mysql:///test”);
bds.setUsername(“root”);
bds.setPassword(“123456”);
bds.setInitialSize(1);
bds.setMaxActive(1);
//bds.setMinIdle(1);
Connection con = bds.getConnection();
System.out.println(con);
con.close();
con = bds.getConnection();
System.out.println(con);
con.close();
C3P0数据库连接池:
ComboPooledDataSource cds = new ComboPooledDataSource();
cds.setDataSourceName(“org.gjt.mm.mysql.Driver”);
cds.setJdbcUrl(“jdbc:mysql:///test”);
cds.setUser(“root”);
cds.setPassword(“123456”);
cds.setInitialPoolSize(10);
cds.setMaxPoolSize(50);
cds.setMinPoolSize(2);
cds.setMaxStatements(200);
Connection con = cds.getConnection();
System.out.println(con);
con.close();
对于共享资源,有一个很著名的设计模式:资源池(Resource Pool)。
该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决我们的问题,可以采用数据库连接池技术。
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。
连接池关键问题分析
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)lock(C#)关键字即可确保线程是同步的。使用方法可以参考,相关文献。
2、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
我们知道当2个线程公用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
3、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他(如何能找到最合适的连接文章将在关键议题中指出);如果没有就抛出一个异常给用户,List中连接是否可以被分配由一个线程来专门管理捎后我会介绍这个线程的具体实现。
4、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
关键议题
引用记数
在分配、释放策略对于有效复用连接非常重要,我们采用的方法也是采用了一个很有名的设计模式:Reference Counting(引用记数)。该模式在复用资源方面用的非常广泛,我们把该方法运用到对于连接的分配释放上。每一个数据库连接,保留一个引用记数,用来记录该连接的使用者的个数。具体的实现上,我们对Connection类进行进一步包装来实现引用记数。被包装的Connection类我们提供2个方法来实现引用记数的操作,一个是Repeat(被分配出去)Remove(被释放回来);然后利用RepeatNow属性来确定当前被引用多少,具体是哪个用户引用了该连接将在连接池中登记;最后提供IsRepeat属性来确定该连接是否可以使用引用记数技术。一旦一个连接被分配出去,那么就会对该连接的申请者进行登记,并且增加引用记数,当被释放回来时候就删除他已经登记的信息,同时减少一次引用记数。
这样做有一个很大的好处,使得我们可以高效的使用连接,因为一旦所有连接都被分配出去,我们就可以根据相应的策略从使用池中挑选出一个已经正在使用的连接用来复用,而不是随意拿出一个连接去复用。
(二)
连接池用于创建和管理数据库连接的缓冲池技术,缓冲池中的连接可以被任何需要他们的线程使用。当一个线程需要用JDBC对一个数据库操作时,将从池中请求一个连接。当这个连接使用完毕后,将返回到连接池中,等待为其他的线程服务。
连接池的主要优点有以下三个方面。
第一、减少连接创建时间。连接池中的连接是已准备好的、可重复使用的,获取后可以直接访问数据库,因此减少了连接创建的次数和时间。
第二、简化的编程模式。当使用连接池时,每一个单独的线程能够像创建一个自己的JDBC连接一样操作,允许用户直接使用JDBC编程技术。
第三、控制资源的使用。如果不使用连接池,每次访问数据库都需要创建一个连接,这样系统的稳定性受系统连接需求影响很大,很容易产生资源浪费和高负载异常。连接池能够使性能最大化,将资源利用控制在一定的水平之下。连接池能控制池中的连接数量,增强了系统在大量用户应用时的稳定性。
下面,简单的阐述下连接池的工作原理。
连接池技术的核心思想是连接复用,通过建立一个数据库连接池以及一套连接使用、分配和管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。
连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、连接池的关闭。
第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。
第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。
连接池用于创建和管理数据库连接的缓冲池技术,缓冲池中的连接可以被任何需要他们的线程使用。当一个线程需要用JDBC对一个数据库操作时,将从池中请求一个连接。当这个连接使用完毕后,将返回到连接池中,等待为其他的线程服务。 连接池的主要优点有以下三个方面。
第一、减少连接创建时间。连接池中的连接是已准备好的、可重复使用的,获取后可以直接访问数据库,因此减少了连接创建的次数和时间。
第二、简化的编程模式。当使用连接池时,每一个单独的线程能够像创建一个自己的JDBC连接一样操作,允许用户直接使用JDBC编程技术。
第三、控制资源的使用。如果不使用连接池,每次访问数据库都需要创建一个连接,这样系统的稳定性受系统连接需求影响很大,很容易产生资源浪费和高负载异常。连接池能够使性能最大化,将资源利用控制在一定的水平之下。连接池能控制池中的连接数量,增强了系统在大量用户应用时的稳定性。
下面,简单的阐述下连接池的工作原理。
连接池技术的核心思想是连接复用,通过建立一个数据库连接池以及一套连接使用、分配和管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。
连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、连接池的关闭。
第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。
第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。 当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。
连接池的基本工作原理
1、基本概念及原理
由上面的分析可以看出,问题的根源就在于对数据库连接资源的低效管理。我们知道,对于共享资源,有一个很著名的设计模式:资源池(Resource Pool)。该模式正是为了解决资源的频繁分配?释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量?使用情况,为系统开发?测试及性能调整提供依据。
2、服务器自带的连接池
JDBC的API中没有提供连接池的方法。一些大型的WEB应用服务器如BEA的WebLogic和IBM的WebSphere等提供了连接池的机制,但是必须有其第三方的专用类方法支持连接池的用法。
连接池关键问题分析
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为Java语言自身提供了对并发管理的支持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:
public synchronized Connection getConnection() 2、多数据库服务器和多用户 对于大型的企业级应用,常常需要同时连接不同的数据库(如连接Oracle和Sybase)。如何连接不同的数据库呢?我们采用的策略是:设计一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件,其中资源文件中存放着多个数据库的url地址()?用户名()?密码()等信息。如tx.url=172.21.15.123:5000/tx_it,tx.user=yang,tx.password=yang321。根据资源文件提供的信息,创建多个连接池类的实例,每一个实例都是一个特定数据库的连接池。连接池管理类实例为每个连接池实例取一个名字,通过不同的名字来管理不同的连接池。 对于同一个数据库有多个用户使用不同的名称和密码访问的情况,也可以通过资源文件处理,即在资源文件中设置多个具有相同url地址,但具有不同用户名和密码的数据库连接信息。
3、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
在Java语言中,Connection类本身提供了对事务的支持,可以通过设置Connection的AutoCommit属性为false,然后显式的调用commit或rollback方法来实现。但要高效的进行Connection复用,就必须提供相应的事务支持机制。可采用每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。
4、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给他(实际是先做连接是否有效的判断,如果可用就分配给用户,如不可用就把这个连接从空闲池删掉,重新检测空闲池是否还有连接);如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxConn),如果没有达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果等待时间超过预定时间timeout,则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池。对于空闲连接的状态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法。
5、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConn)和最大连接数(maxConn)来控制连接池中的连接。最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
连接池的实现
1、连接池模型
本文讨论的连接池包括一个连接池类(DBConnectionPool)和一个连接池管理类(DBConnetionPoolManager)。连接池类是对某一数据库所有连接的“缓冲池”,主要实现以下功能:①从连接池获取或创建可用连接;②使用完毕之后,把连接返还给连接池;③在系统关闭前,断开所有连接并释放连接占用的系统资源;④还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题),并能够限制连接池中的连接总数不低于某个预定值和不超过某个预定值。
连接池管理类是连接池类的外覆类(wrapper),符合单例模式,即系统中只能有一个连接池管理类的实例。其主要用于对多个连接池对象的管理,具有以下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,创建连接池对象;③为方便管理多个连接池对象,为每一个连接池对象取一个名字,实现连接池名字与其实例之间的映射;④跟踪客户使用连接情况,以便需要是关闭连接释放资源。连接池管理类的引入主要是为了方便对多个连接池的使用和管理,如系统需要连接不同的数据库,或连接相同的数据库但由于安全性问题,需要不同的用户使用不同的名称和密码。
2、连接池实现
下面给出连接池类和连接池管理类的主要属性及所要实现的基本接口:
复制内容到剪贴板
代码:
public class DBConnectionPool implements TimerListener{
private int checkedOut;//已被分配出去的连接数
private ArrayList freeConnections=new ArrayList();
//容器,空闲池,根据//创建时间顺序存放已创建但尚未分配出去的连接
private int minConn;//连接池里连接的最小数量
private int maxConn;//连接池里允许存在的最大连接数
private String name;//为这个连接池取个名字,方便管理
private String password;//连接数据库时需要的密码
private String url;//所要创建连接的数据库的地址
private String user;//连接数据库时需要的用户名
public Timer timer;//定时器
public DBConnectionPool(String name,String URL,String user,
String password,int maxConn)//公开的构造函数
public synchronized void freeConnection(Connection con)
//使用完毕之后,把连接返还给空闲池
public synchronized Connection getConnection(long timeout)
//得到一个连接,timeout是等待时间
public synchronized void release()
//断开所有连接,释放占用的系统资源
private Connection newConnection()
//新建一个数据库连接
public synchronized void TimerEvent()
//定时器事件处理函数
}
public class DBConnectionManager {
static private DBConnectionManager instance;
//连接池管理类的唯一实例
static private int clients;//客户数量
private ArrayList drivers=new ArrayList();
//容器,存放数据库驱动程序
private HashMap pools = new HashMap();
//以name/value的形式存取连接池对象的名字及连接池对象
static synchronized public DBConnectionManager getInstance()
/*如果唯一的实例instance已经创建,直接返回这个实例;否则,调用私有构造函数,创建连接池管理类的唯一实例/
private DBConnectionManager()
//私有构造函数,在其中调用初始化函数init()
public void freeConnection(String name,Connection con)
//释放一个连接,name是一个连接池对象的名字
public Connection getConnection(String name)
//从名字为name的连接池对象中得到一个连接
public Connection getConnection(String name,long time)
//从名字为name的连接池对象中取得一个连接,time是等待时间
public synchronized void release()//释放所有资源
private void createPools(Properties props)
//根据属性文件提供的信息,创建一个或多个连接池
private void init()//初始化连接池管理类的唯一实例,由私有构造函数调用
private void loadDrivers(Properties props)//装载数据库驱动程序
}
3、连接池使用
上面所实现的连接池在程序开发时如何应用到系统中呢?下面以Servlet为例说明连接池的使用。
Servlet的生命周期是:在开始建立servlet时,调用其初始化(init)方法。之后每个用户请求都导致一个调用前面建立的实例的service方法的线程。最后,当服务器决定卸载一个servlet时,它首先调用该servlet的destroy方法。
根据servlet的特点,我们可以在初始化函数中生成连接池管理类的唯一实例(其中包括创建一个或多个连接池)。如:
复制内容到剪贴板
代码:
public void init() throws ServletException {
connMgr=DBConnectionManager.getInstance();
}
然后就可以在service方法中通过连接池名称使用连接池,执行数据库操作。最后在destroy方法中释放占用的系统资源,如:
复制内容到剪贴板
代码:
public void destroy(){
connMgr.release();
super.destroy();
}
结束语
在使用JDBC进行与数据库有关的应用开发中,数据库连接的管理是一个难点。很多时候,连接的混乱管理所造成的系统资源开销过大成为制约大型企业级应用效率的瓶颈。对于众多用户访问的Web应用,采用数据库连接技术的系统在效率和稳定性上比采用传统的其他方式的系统要好很多。
一、 线程池的原理:
线程池,究竟是怎么一回事?其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:
先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。
可能你也许会问:为什么要搞得这么麻烦,如果每当客户端有新的请求时,我就创建一个新的线程不就完了?这也许是个不错的方法,因为它能使得你编写代码相对容易一些,但你却忽略了一个重要的问题——性能!
就拿我所在的单位来说,我的单位是一个省级数据大集中的银行网络中心,高峰期每秒的客户端请求并发数超过100,如果为每个客户端请求创建一个新线程的话,那耗费的CPU时间和内存将是惊人的,如果采用一个拥有200个线程的线程池,那将会节约大量的的系统资源,使得更多的CPU时间和内存用来处理实际的商业应用,而不是频繁的线程创建与销毁。
二、 数据库连接池
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。
一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
1) 最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;
2) 最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
三、 linux线程池的C语言实现
通常我们使用多线程的方式是,需要时创建一个新的线程,在这个新的线程里执行特定的任务,然后在任务完成后退出。这在一般的应用里已经能够满足我们应用的需要,毕竟我们并不是什么时候都需要创建大量的线程,并在它们执行一个简单的任务后销毁。
但是在一些web、email、database等应用里,比如彩铃,我们的应用在任何时候都要准备应对数目巨大的连接请求,同时,这些请求所要完成的任务却又可能非常的简单,即只占用很少的处理时间。这时,我们的应用有可能处于不停的创建线程并销毁线程的状态。虽说比起进程的创建,线程的创建时间已经大大缩短,但是如果需要频繁的创建线程,并且每个线程所占用的处理时间又非常简短,则线程创建和销毁带给处理器的额外负担也是很可观的。
线程池的作用正是在这种情况下有效的降低频繁创建销毁线程所带来的额外开销。一般来说,线程池都是采用预创建的技术,在应用启动之初便预先创建一定数目的线程。应用在运行的过程中,需要时可以从这些线程所组成的线程池里申请分配一个空闲的线程,来执行一定的任务,任务完成后,并不是将线程销毁,而是将它返还给线程池,由线程池自行管理。如果线程池中预先分配的线程已经全部分配完毕,但此时又有新的任务请求,则线程池会动态的创建新的线程去适应这个请求。当然,有可能,某些时段应用并不需要执行很多的任务,导致了线程池中的线程大多处于空闲的状态,为了节省系统资源,线程池就需要动态的销毁其中的一部分空闲线程。因此,线程池都需要一个管理者,按照一定的要求去动态的维护其中线程的数目。
基于上面的技术,线程池将频繁创建和销毁线程所带来的开销分摊到了每个具体执行的任务上,执行的次数越多,则分摊到每个任务上的开销就越小。
当然,如果线程创建销毁所带来的开销与线程执行任务的开销相比微不足道,可以忽略不计,则线程池并没有使用的必要。比如,FTP、Telnet等应用时。
使用线程池的好处:
1.减少在创建和销毁线程上所花的时间以及系统资源的开销
2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存
以下是Java自带的几种线程池:
1、newFixedThreadPool 创建一个指定工作线程数量的线程池。
每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
2、newCachedThreadPool 创建一个可缓存的线程池。
这种类型的线程池特点是:
1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
3、newSingleThreadExecutor 创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。
单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的 。
4、newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。
总结:
一.FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
二.CachedThreadPool的特点就是在线程池空闲时,即线程池中没有可运行任务时,它会释放工作线程,从而释放工作线程所占用的资源。但是,但当出现新任务时,又要创建一新的工作线程,又要一定的系统开销。并且,在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。