摘要:假设有这样一个情形:客户端需要通过慢速网络链接调用同一数据。尽管数据库可以立即将结果从缓存发送到客户端,但结果必须通过线路传送到客户端,这就增加了整体执行时间。现在有一些专门的中间件框架(如 Oracle Coherence),用于在 Java、PHP 和 Ruby 中缓存数据,如果有一个在客户端级别缓存数据的通用方法,又将如何呢?

一。初识高速缓存和连接池
设想这样一种情形:你突然口渴,需要一杯水来缓解,从心情上来讲,当然是越快越好 了。通常,一杯水的产生包括从水源(井水、河水或江水、甚至海水等)抽取,通过管 道传输和设备净化,才到达你饮水的容器中。上述过程是必须的,但并不是每一杯水的 产生都必须把上述过程重复一次。你可以用一个大一点的容器(例如缸或罐等)来盛大 量的水,喝水之前分到杯子小部分中即可,你的代价只是把水从缸转移到杯子;你还可 以在大量用水(例如洗澡或洗衣服等)时,只需打开水阀,而不必临时铺设通往水源的 管道和购买净化水的设备。因为水是人们生活不可缺少的东西,每时每刻都在被大量地 使用,而且物理本质也完全相同,所以政府会铺设管道和建设水处理站,完成那些比较 困难的工作,达到资源共享的目的,而你也可针对自己的需求,用容器来盛那些具有特 定用途的水。本文将要和你讨论的高速缓存和连接池与上述特定容器和传输管道有很多 相似之处,它们都达到了同一个目的:在满足用户意愿的前提下,尽可能地共享资源, 以提高整个系统的性能。 高速缓存和连接池是数据访问中的重要技术,某些情况下的应用对访问数据库的性能有 巨大的提高,而且都得到了数据库业界的普遍支持。前者由 DBMS 厂商针对自己的数据库 实现,提供可供用户配置的方案;后者是 JDBC 的一个标准接口,由支持 J2EE 技术的应用 服务器厂商提供具体的实现,而你的 Java 程序代码无需更改。本文将向你简单介绍高速 缓存和连接池的概念和机制,并以 PointBase 数据库为例向你展示高速缓存的应用,而一个简单的连接池应用场景将向你描述应用的条件和提高的性能。[@more @] 二.Cache(高速缓存)和 Connection Pool(连接池)的概念和机制.
它们不是数据库独有的技术,但却得到数据库业界的普遍支持,并在其它数据存取和对 象复用领域有很多类似的应用。

1.Cache(高速缓存)
作为个人计算机的日常使用者,你肯定听说过这些名词:Cache(高速缓存)、Memory( 内存)、Hard disk(硬盘)。它们都是数据存取单元,但存取速度却有很大差异,呈依 次递减的顺序。对于 CPU 来说,它可以从距离自己最近的 Cache 高速地存取数据,而不是 从内存和硬盘以低几个数量级的速度来存取数据。而 Cache 中所存储的数据,往往是 CPU 要反复存取的数据,有特定的机制(或程序)来保证 Cache 内数据的命中率(Hit Rate)
。因此,CPU 存取数据的速度在应用高速缓存后得到了巨大的提高。
对于数据库来说,厂商的做法往往是在内存中开辟相应的区域来存储可能被多次存取的 数据和可能被多次执行的语句,以使这些数据在下次被访问时不必再次提交对 DBMS 的请 求和那些语句在下次执行时不必再次编译。
因为将数据写入高速缓存的任务由 Cache Manager 负责,所以对用户来说高速缓存的内容 肯定是只读的。需要你做的工作很少,程序中的 SQL 语句和直接访问 DBMS 时没有分别,返 回的结果也看不出有什么差别。而数据库厂商往往会在 DB Server 的配置文件中提供与 C ache 相关的参数,通过修改它们,可针对我们的应用优化 Cache 的管理。下图是在 Win2K 中配置 MS Access 数据源的界面,在 "驱动程序" 部分你可设置的页超时和缓冲区大小就是 和 Cache 有关的参数。在后面的讨论中,我将展示一个更复杂的数据库,向你解释 Cache 对访问数据库性能的影响和如何寻找最优的配置方案。

2.Connection Pool(连接池)
池是一个很普遍的概念,和缓冲存储有机制相近的地方,都是缩减了访问的环节,但它 更注重于资源的共享.
方案:
对于访问数据库来说,建立连接的代价比较昂贵,因此,我们有必要建立 "连接池" 以提 高访问的性能。我们可以把连接当作对象或者设备,池中又有许多已经建立的连接,访 问本来需要与数据库的连接的地方,都改为和池相连,池临时分配连接供访问使用,结 果返回后,访问将连接交还。 (在传统的客户端 / 服务器体系结构中,用户会话和数据库连接之间是一对一的通信。但在基于 Web 的系统中,情况可能有所不同。基于 Web 的系统在本质上是 “无状态” 的 — 当您访问页面时,将建立与数据库的连接,页面下载完成后,将切断与数据库的连接。稍后,当用户再次单击页面时,将建立一个新的连接,目的达到后又将切断连接。这一过程使得没有必要维护大量的同步连接。建立连接的开销很大,因此连接池是这些应用程序的一个重要要求。在这一模式中,当页面需要数据库访问时,会从池中分配一个已经建立的连接。完成工作后,Web 会话会将连接返回池中。)
JDBC 1.0 标准及其扩展中没有定义连接池,而在 JDBC 2.0 标准的扩展中定义了与连接池 相关的接口。与接口对应的类由应用服务器厂商实现,你可在对服务器的管理过程中调 节某个数据库连接池的参数。
而要了解 Java 连接池我们先要了解数据库连接池(connection pool)的原理,Java 连接池正是数据库连接池在 Java 上的应用。—— 我们知道,对于共享资源,有一个很著名的设计模式:资源池(Resource Pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个 “缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从 “缓冲池” 中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。
C3P0 是一个开放源代码的 JDBC 连接池,它在 lib 目录中与 Hibernate 一起发布,包括了实现 jdbc3 和 jdbc2 扩展规范说明的 Connection 和 Statement 池的 DataSources 对象。(主页:http://sourceforge.net/projects/c3p0/)
BoneCP 是一个开源的快速的 JDBC 连接池。BoneCP 很小,只有四十几 K(运行时需要 log4j 和 Google Collections 的支持,这二者加起来就不小了),而相比之下 C3P0 要六百多 K。另外个人觉得 BoneCP 有个缺点是,JDBC 驱动的加载是在连接池之外的,这样在一些应用服务器的配置上就不够灵活。当然,体积小并不是 BoneCP 优秀的原因,BoneCP 到底有什么突出的地方呢,请看看性能测试报告。(主页:http://jolbox.com/)
DBCP (Database Connection Pool)是一个依赖 Jakarta commons-pool 对象池机制的数据库连接池,Tomcat 的数据源使用的就是 DBCP。目前 DBCP 有两个版本分别是 1.3 和 1.4。1.3 版本对应的是 JDK 1.4-1.5 和 JDBC 3,而 1.4 版本对应 JDK 1.6 和 JDBC 4。因此在选择版本的时候要看看你用的是什么 JDK 版本了,功能上倒是没有什么区别。(主页:http://commons.apache.org/dbcp/)
Proxool 是一个 Java SQL Driver 驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健壮。可以透明地为你现存的 JDBC 驱动程序增加连接池功能。(主页:http://proxool.sourceforge.net/)

二.Case
1. 高速缓存的参数设定

在 PointBase 数据库 DB Server 的配置参数列表中,我们可以找到这几个参数:cache.ch
eckpointinterval、cache.size、SQLCaching.size 等。下表是对各个参数的描述:


# 参数名 参数描述

# cache.checkpointinterval 检查点的时间间隔
cache.size 高速缓存的最大页数(素数时,性能最好)
SQLCaching.size 高速缓存中 SQL 语句的个数

对于 cache.checkpointinterval 来说,和前面 Access 界面中的页超时是一个概念,它指
定了页面内容更新的时间间隔,这取决于你的应用对时效性的要求程度。
对于 cache.size 来说,指定了页面的个数,一般应设定为符合你查询结果的需求。至于 为何是素数,我也纳闷,不过还是遵照厂商的指示吧。
对于 SQLCaching.size 来说,指定了存储的经过编译的 SQL 语句的个数,你可以把它设定 为 0,从而取消这个选项。
使用 Cache 后,性能到底有多大提高?我打开 PointBase 的 Console 通过 JDBC 驱动访问 Ser
ver,将 SQL 菜单下的 Timing Mode 选上,以显示各个步骤的耗费时间。执行语句是:
SELECT * FROM PRODUCT_TBL
第一次访问,总计耗时 1082 毫秒,而编译耗时 771 毫秒。
紧接着的第二次访问,总计耗时仅为 160 毫秒,而编译耗时为 0。
再接着的第三次访问,总计耗时仅为 91 毫秒,编译耗时也为 0。
关闭 Console,等待超过 30 秒之后,重新开启 Console,执行相同的语句,总计耗时 210 毫 秒,编译耗时为 20 毫秒。
自等待超过 30 秒之后,执行语句,总计耗时 101 毫秒,编译耗时为 0。
由此可以看出,高速缓存的应用大大提高了访问数据库的性能,而其参数的设定则要依 据前面对它们的描述来进行,需要你仔细阅读数据库的配置文档。

2. 连接池的设置和应用

我选择 IBM 公司的应用服务器平台 WebSphere 来给大家演示连接池的设置,使你面对友好
的 Web 界面,可以体验到非常简易的操作场景。
首先,我们进入 WebSphere 的管理控制台,这是一个非常漂亮的 Web 界面:
紧接着,我选定一个数据源:Session Persistence datasource,就可看到这个数据源
的属性配置了。在这儿,仅仅列举和连接池有关的属性:
Minimum Pool Size 池中保持的连接的最小数目;有新的请求,且没有激活连接可供用时,池中连接数将增大,到最大连接数为止
Maximum Pool Size 池中保持的连接的最大数目;当这个数目达到,且没有激活连接供使用时,新的请求将等待
Connection Timeout 当连接数达到最大值,且激活连接都在被使用时,新的请求等待时间
Idle Timeout 连接可在池中闲置的时间;超过将释放资源,到最小连接数为止
Orphan Timeout 连接在被应用控制时,可闲置的时间;超过将返回池中
你可以根据需要来修改这些数值,以满足你的应用需要。接下来,我们讨论一下连接池 的应用。

i).EJB 访问数据库(场景 1,使用 JDBC 1.0)

import java.sql.*; 
import javax.sql.*; 
... 
public class AccountBean implements EntityBean { 
... 
public Collection ejbFindByLastName(String lName) { 
try { 
String dbdriver = new initialContext().lookup("java:comp/env/DBD 
RIVER").toString(); 
Class.forName(dbdriver).newInstance(); 
Connection conn = null; 
conn = DriverManager.getConnection("java:comp/env/DBURL", "userI 
D", "password"); 
... 
conn.close(); 
} 
... 
}

如果 EntityBean 是一个共享组件,那么每次客户请求时,都要建立和释放与数据库的连
接,这成为影响性能的主要问题。

2).EJB 访问数据库(场景 2,使用 JDBC 2.0)

import java.sql.*; 
import javax.sql.*; 
// import here vendor specific JDBC drivers 
public ProductPK ejbCreate() { 
try { 
// initialize JNDI lookup parameters 
Context ctx = new InitialContext(parms); 
... 
ConnectionPoolDataSource cpds = (ConnectionPoolDataSource)ctx.lo 
okup(cpsource); 
... 
// Following parms could all come from a JNDI look-up 
cpds.setDatabaseName("PTDB"); 
cpds.setUserIF("XYZ"); 
... 
PooledConnection pc = cpds.getPooledConnection(); 
Connection conn = pc.getConnection(); 
... 
// do business logic 
conn.close(); 
} 
... 
}

EJB 组件利用 JNDI 的 lookup () 方法定位数据库的连接池资源,利用 getConnection () 方法
得到已经打开的数据库连接,而用 close () 来释放连接,放回池中。因此,与场景 1 相比 ,少了与数据库建立物理连接的损耗。对于原本要频繁打开和关闭物理连接的应用来说 ,通过这种建立逻辑连接并复用的方法,性能肯定能够得到大幅度提高。

四。性能问题的深远思索

性能问题并不局限于数据库的应用上,而是存在于每个软件系统中。我们希望软件系统 付出的最小,而获得的最大,因而无时无刻不在优化它们。通过《Java 程序访问数据库 的速度瓶颈问题的分析和解决》和本文,我对 Java 程序访问数据库的性能问题做了分析 ,并提供了优化 Java 程序的解决方案,希望对你有所帮助。 也许你会关心碰到类似的性能问题时应如何分析和解决,我就针对此次探讨 "访问数据库 的速度瓶颈" 问题的过程中碰到的难题、网友的意见和自己的体会,作一个关于 " 方法论
" 的经验总结,希望能够抛砖引玉,更好地解决类似的问题。
解决性能问题的几条经验
1、 不要让硬件的低配置成为软件正常运行的障碍,后者有升级前者的需求,请立即满 足;经常碰到这样的问题 "P166+64M 的机子跑 Win2K+MySql+JBoss,能跑么?" 我在回答 "
可以" 的同时只有对着屏幕发呆了。
2、 尽量使用商业软件,并享受良好的售后技术支持;如果你没有黑客精神,请不要使 用自由软件。
3、 分析好自己的问题,也许它的本质和他人的不同;没有一把钥匙可打开的任一把锁 。
4、 确定瓶颈环节的位置;解决了瓶颈问题,往往整个的性能问题就解决了,千万不要 抓着边缘的问题不放。
5、 将瓶颈环节细分为多个顺序的流程,用逐个替代的方法来试探瓶颈的核心位置;细 分问题使你都问题有更进一步的了解。
6、 做自己能做的和该做的事情,始终面向自己的现实问题;不要尝试那些应该由厂商 解决的问题(例如,自己写个 JDBC 驱动)。
7、 他人的方案只供自己参考,解决要靠自己思考;你我应用的情形不同,应用解决方 案要在理解他人的方案之后。
8、 观察新技术,应用新技术;它往往包含了前人对问题解决的思路,只是对你来说不 可见。
9、 问题得到解决后,立即罢手,并汇报结果;在现实问题得到解决后,没有必要花费精力在非核心的问题上,也许它们永远不会被碰到,不要假想问题让自己解决。上述经验为个人即兴的总结,并未有严谨的逻辑推导,仅代表我解决技术问题的思路, 供你碰到类似问题时参考。 可以提前告诉大家的是,下一篇文章我将把主题转移到 JDO 的介绍和讨论上。这个陌生的 API 已经进入 Final Draft 了,可它到底能够给数据存取带来什么好处,和 JDBC 有什么关 联的地方,《JDO(Java Data Object)的发展和介绍》将向你娓娓道来。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Jalen Chu 微信支付

微信支付

Jalen Chu 支付宝

支付宝

Jalen Chu 公众号

公众号