 | 级别: 中级 Tom Seelbach (seelbach@us.ibm.com), 资深软件工程师, IBM
2005 年 11 月 14 日 本文介绍了在使用 Apache Derby 开放源码数据库管理系统(DBMS)作为 Globus Toolkit 4.0(GT4)中的 Reliable File Transfer(RFT)服务的数据库管理系统时,所要对代码和配置进行的修改。GT4 目前可以支持 PostgreSQL 和 MySQL DBMS。由于 Derby 是一个 100% 的 Java™ 技术系统,因此它可以嵌入到 GT4 发行版中,这可以极大地简化网格的安装、配置和操作过程。GT4 中需要 DBMS 的核心组件是 RFT。
为什么要使用 Derby?
在 GT4 中使用 Derby 有几个原因:
- Derby 是使用 Java 编程语言编写的,这意味着它不需要对每个支持的平台都进行编译。
- Derby 设计用来嵌入到大型系统中,例如 GT4。
- 实际上不需要设置。嵌入式的 Derby DBMS 可以自动启动。
- Derby 非常小。核心的 jar 文件不到 2MB。
- Derby 支持大部分 SQL 标准,完全可以用作 RDBMS 系统。如果需要,可以很容易地切换到其他的 DBMS。
- Derby 和 GT4 都是遵循 Apache 许可证发布的。
修改概述
您需要做的修改主要有 3 类:模式、配置和代码。
- 在模式中,所支持的数据类型
BOOLEAN/BIT 和 TEXT 中存在区别。
- 在代码中,修改主要是对 null 的处理、可更新的游标以及对模式的修改。
- 从配置的观点来看,修改更加简单了。我们将 Derby 作为一个嵌入式数据库运行,因此只需要很少的设置。
这些修改在将来的 GT4 发行版中应该就可以使用了。有关 GT4 中对 Derby 支持的可用性的更多信息,请访问 The Globus Alliance。问题和意见可以通过 GT4 Friends 讨论列表提出。
GT4 配置的修改
安装并配置 Derby
注意:我使用了 V10.1.1.0 版本的 Derby,这可以从 Apache.org 上下载。
安装 Globus Toolkit V4.0.1,并将 GLOBUS_LOCATION 环境变量设置为您安装的根目录,然后:
- 解压 Derby 发行版,并将 derby.jar、derbyclient.jar 和 derbytools.jar 拷贝到 $GLOBUS_LOCATION/lib/ 中
- 创建一个目录 $GLOBUS_LOCATION/database/
- 将下面的代码添加到 etc/globus-user-env.sh 中:
清单 1. globus-user-env.sh 中添加的内容
#
#The following statement will set the jvm options for the database
#
export GLOBUS_OPTIONS=-Dderby.system.home=$GLOBUS_LOCATION/database/
#
export CLASSPATH=$GLOBUS_LOCATION/lib/derby.jar:
$GLOBUS_LOCATION/lib/derbytools.jar:$GLOBUS_LOCATION/lib/derbyclient.jar
|
- 导出 GLOBUS_LOCATION 的设置,并引用 globus-user-env.sh 文件。要确保环境变量都正确设置了,请检查
CLASSPATH 和 GLOBUS_* 环境变量。例如:
清单 2. 检查环境变量
env | grep CLASS
CLASSPATH=/opt/gt401f/lib/derby.jar:/opt/gt401f/lib/derbytools.jar:/
opt/gt401f/lib/derbyclient.jar
env | grep GLOBUS
GLOBUS_PATH=/opt/gt401f
GLOBUS_LOCATION=/opt/gt401f
GLOBUS_OPTIONS=-Dderby.system.home=/opt/gt401f/database
|
- 要连接到 Derby 而不是其他数据库上,请修改 etc/globus_wsrf_rft/jndi-config.xml 文件中
driverName 和 connectionString 的设置:
org.apache.derby.jdbc.EmbeddedDriver
jdbc:derby:directory:rftDatabase
|
修改模式,创建数据库
我们首先从 GT4 MySQL 模式开始入手,这可以在文件 GLOBUS_LOCATION/share/globus_wsrf_rft/rft_schema_mysql.sql 中找到,我们将其拷贝到 rft_schema_derby.sql 文件。与 PostgreSQL 模式相比,MySQL 模式的语义与 Derby 更为接近。具体来说,PostgreSQL 模式包含了 TEXT 类型的域。它们可以转换成 MySQL 模式中的标准 SQL VARCHAR 数据类型,因此也可以使用 Derby。
MySQL 模式中有几个使用 BIT 数据类型定义的域。在 Derby 中我们将把这些域重新定义为 CHAR(1)。以下域在 MySQL 模式中被定义为 bit:
清单 3. bit 域
started bit(1) default 0,
all_or_none bit(1) default 0,
dcau bit(1) default 0,
notpt bit(1) default 0,
binary_mode bit(1) default 1,
ignore_file_perm_errors bit(1) default 0
|
我们还需要对两处进行修改。首先,权限域被定义为 INT。将其修改为 VARCHAR。(这些修改会反映到将来的 Globus 发行版中)。其次,主键约束名重名了。对于 Derby 来说,主键约束名要求是惟一的。一个完整的 RFT SQL 模式如下所示:
清单 4. Derby 的完整 RFT SQL 模式
CREATE TABLE "GLOBUS"."REQUEST" (
"ID" INTEGER NOT NULL,
"CONCURRENCY" INTEGER DEFAULT 1,
"TERMINATION_TIME" DOUBLE,
"PROXY_LOC" VARCHAR(200),
"USERNAME" VARCHAR(200),
"STARTED" CHAR(1),
"ALL_OR_NONE" CHAR(1),
"MAXATTEMPTS" INTEGER DEFAULT 1,
"DELEGATED_EPR" VARCHAR(1255),
"USER_SUBJECT" VARCHAR(200));
CREATE TABLE "GLOBUS"."TRANSFER" (
"ID" INTEGER NOT NULL,
"REQUEST_ID" INTEGER NOT NULL,
"SOURCE_URL" VARCHAR(200) NOT NULL,
"DEST_URL" VARCHAR(200),
"STATUS" INTEGER DEFAULT 4,
"ATTEMPTS" INTEGER DEFAULT 0,
"USER_NAME" VARCHAR(100) DEFAULT NULL,
"DCAU" CHAR(1),
"PARALLEL_STREAMS" INTEGER DEFAULT 1,
"TCP_BUFFER_SIZE" INTEGER DEFAULT 0,
"BLOCK_SIZE" INTEGER DEFAULT 16000,
"NOTPT" CHAR(1),
"BINARY_MODE" CHAR(1),
"SOURCE_SUBJECT" VARCHAR(200),
"DEST_SUBJECT" VARCHAR(200),
"RETRY_TIME" DOUBLE DEFAULT 0,
"SIZE" DOUBLE DEFAULT 0,
"FAULT" VARCHAR(255),
"PERMISSIONS" VARCHAR(100),
"IGNORE_FILE_PERM_ERRORS" CHAR(1)
);
CREATE TABLE "GLOBUS"."TRANSFERID" (
"TRANSFER_ID" INTEGER NOT NULL
);
CREATE TABLE "GLOBUS"."FACTORY" (
"TOTAL_TRANSFERS" DOUBLE,
"TOTAL_BYTES" DOUBLE
);
CREATE TABLE "GLOBUS"."RESTART" (
"TRANSFER_ID" INTEGER,
"MARKER" VARCHAR(200)
);
CREATE TABLE "GLOBUS"."REQUESTID" (
"REQUEST_ID" INTEGER NOT NULL
);
-- ----------------------------------------------
-- DDL Statements for keys
-- ----------------------------------------------
-- primary/unique
ALTER TABLE "GLOBUS"."TRANSFER"
ADD CONSTRAINT "TRANSFER_PK_CONS" PRIMARY KEY ("ID");
ALTER TABLE "GLOBUS"."REQUEST"
ADD CONSTRAINT "REQUEST_PK_CONS" PRIMARY KEY ("ID");
ALTER TABLE "GLOBUS"."TRANSFERID"
ADD CONSTRAINT "TRANSFER_ID_PK_CONS" PRIMARY KEY ("TRANSFER_ID");
ALTER TABLE "GLOBUS"."REQUESTID"
ADD CONSTRAINT "REQUEST_ID_PK_CONS" PRIMARY KEY ("REQUEST_ID");
|
使用 IJ 创建数据库,这是 Derby 的交互式 JDBC 脚本工具。
清单 5. 使用 IJ 创建数据库
java -classpath $GLOBUS_LOCATION/lib/derby.jar:
$GLOBUS_LOCATION/lib/derbytools.jar
-Dderby.infolog.append=true
-Dij.protocol=jdbc:derby:directory:$GLOBUS_LOCATION/database/
-D'ij.database=rftDatabase;create=true' org.apache.derby.tools.ij
$GLOBUS_LOCATION/share/globus_wsrf_rft/rft_schema_derby.sql
|
RFT 代码的修改
所有与数据库有关的代码都包含在两个类中,可以在 src/org/globus/transfer/reliable/service/ 目录中找到这两个类:
- RFTResourceManager.java
- ReliableFileTransferDbAdapter.java
对 RFTResourceManager.java 的修改
对 RFTResourceManager 的修改如下:
-
SQL 限制子句 在 Derby 中并不受支持。相反,我们使用
Statement.getMaxRows() 方法。
-
可更新游标 —— 在撰写本文时,Derby 还不支持可更新的游标。因此我们使用一个新方法来替换
updateInt() 和 updateRow() 调用,这就是 updateTransferJob(),它使用一个新的 PreparedStatement 来实现更新。
它与 1.22.4.5 版本的 RFTResourceManager.java 之间的区别如下:
清单 6. 对 RFTResourceManager.java 的修改
93,96c93
+ + " order by id ";
+
+ private static final String updateTransferQuery =
+ "UPDATE transfer set status=? where id=?";
---
- + " order by id limit ?";
223,255d219
+ public
+ synchronized void updateTransferJob(int transferId, int status)
+ throws RftDBException {
+ Connection c = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ c = RFTDatabaseSetup.getDBConnection();
+ preparedStatement =
+ c.prepareStatement(updateTransferQuery);
+ preparedStatement.setObject(
+ 1, new Integer(transferId));
+ preparedStatement.setObject(2,
+ new Integer(status));
+ preparedStatement.executeUpdate();
+ } catch (SQLException e) {
+ logger.error(i18n.getMessage("dbUpdateErr",
+ "" + requestId), e);
+ throw new RftDBException(i18n.getMessage(
+ "dbUpdateErr", "" + requestId), e);
+ } finally {
+ try {
+ if (preparedStatement != null) {
+ preparedStatement.close();
+ }
+ if (c != null) {
+ RFTDatabaseSetup.returnDBConnection(c);
+ }
+ } catch (SQLException sql) {
+ logger.warn(i18n.getMessage(
+ "dbStatementErr"), sql);
+ }
+ }
+ }
269c237
+ statement.setMaxRows(this.cacheSize);
---
- statement.setObject(3, new Integer(this.cacheSize));
271d238
+ for (int i=0;i<this.cacheSize;i++) {
274,275c241
+ int tempTransferId = rs.getInt(1);
+ transfer.setTransferId(new Integer(tempTransferId));
---
- transfer.setTransferId(new Integer(rs.getInt(1)));
298,299c264,265
+ this.updateTransferJob(tempTransferId,
+ RFTConstants.STATUS_CACHED);
---
- rs.updateInt("status",RFTConstants.STATUS_CACHED);
- rs.updateRow();
306d271
+ }
|
对 ReliableFileTransferDbAdapter.java 的修改
对 ReliableFileTransferDbAdapter.java 的修改如下:
-
null 的处理 —— 在 Derby 中,需要显式地在
PreparedStatements 中将对象设置成 null,这需要使用一个诸如 pstmt.setObject(5, "null"); 之类的调用。以前,代码允许在处理可为空的域时采用缺省的行为。现在每个可空的域都会进行检查,并设置适当的缺省值。
-
可更新的游标 —— 与 RFTResourceManager 中的修改类似,但是使用新的语句替换了
updateInt() 和 updateRow() 调用。
-
Boolean 域 —— 回想一下我们将 SQL 模式的某些域从
BIT 修改成了 CHAR。这样,JDBC 调用将 CHAR 域设置成一个 Java Boolean 值,就会导致这个 CHAR 域被设置为 0 或 1。在 MySQL 中可以处理 BIT 域的代码依然可以处理 Derby 中的 CHAR 域。例如,pstmt.setObject(6, new Boolean(true)) 会将 CHAR 域在数据库中设置为 1。
清单 7. 对 ReliableFileTransferDbAdapter.java 的修改
184c184,188
- pstmt.setObject(5, request.getMaxAttempts());
---
+ if (request.getMaxAttempts() == null) {
+ pstmt.setObject(5, new Integer(1));
+ } else {
+ pstmt.setObject(5, request.getMaxAttempts());
+ }
324a329
+ Statement tempStatement = null;
335c340
- Statement tempStatement = c.createStatement();
---
+ tempStatement = c.createStatement();
348,349c353,357
- rs.updateInt("request_id",nextRequestId);
- rs.updateRow();
---
+ String updateQuery =
+ "update requestid set request_id=";
+ updateQuery = updateQuery + nextRequestId;
+ tempStatement = c.createStatement();
+ tempStatement.executeUpdate(updateQuery);
402a410
+ Statement tempStatement = null;
416c424
- Statement tempStatement = c.createStatement();
---
+ tempStatement = c.createStatement();
427,428c435,437
- rs.updateInt("transfer_id",nextTransferId);
- rs.updateRow();
---
+ tempStatement = c.createStatement();
+ tempStatement.executeUpdate(
+ "update transferid set " + "transfer_id= "+ nextTransferId);
482c491,495
- pstmt.setObject(6, tempUserName);
---
+ if (tempUserName == null) {
+ pstmt.setObject(6, "null");
+ } else {
+ pstmt.setObject(6, tempUserName);
+ }
489c502,506
- pstmt.setObject(5, tempUserName);
---
+ if(tempUserName == null) {
+ pstmt.setObject(5, "null");
+ } else {
+ pstmt.setObject(5, tempUserName);
+ }
852,861c869,918
- pstmt.setObject(5, rftOptions.getUserName());
- pstmt.setObject(6, rftOptions.getDcau());
- pstmt.setObject(7, rftOptions.\
getParallelStreams());
- pstmt.setObject(8, rftOptions.\
getTcpBufferSize());
- pstmt.setObject(9, rftOptions.getBlockSize());
- pstmt.setObject(10, rftOptions.getNotpt());
- pstmt.setObject(11, rftOptions.getBinary());
- pstmt.setObject(12, rftOptions.\
getSourceSubjectName());
- pstmt.setObject(13, rftOptions.\
getDestinationSubjectName());
- pstmt.setObject(14, rftOptions.\
getIgnoreFilePermErr());
---
+ if(rftOptions.getUserName() == null) {
+ pstmt.setObject(5, "null");
+ } else {
+ pstmt.setObject(5,\
rftOptions.getUserName());
+ }
+ if(rftOptions.getDcau() == null) {
+ pstmt.setObject(6, new Boolean(true));
+ } else {
+ pstmt.setObject(6, rftOptions.getDcau());
+ }
+ if(rftOptions.getParallelStreams() == null) {
+ pstmt.setObject(7, new Integer(1));
+ } else {
+ pstmt.setObject(7, rftOptions.\
getParallelStreams());
+ }
+ if(rftOptions.getTcpBufferSize() == null) {
+ pstmt.setObject(8, new Integer(16384));
+ } else {
+ pstmt.setObject(8, rftOptions.\
getTcpBufferSize());
+ }
+ if (rftOptions.getBlockSize() == null) {
+ pstmt.setObject(9, new Integer(16384));
+ } else {
+ pstmt.setObject(9,\
rftOptions.getBlockSize());
+ }
+ if (rftOptions.getNotpt() == null) {
+ pstmt.setObject(10, new Boolean(false));
+ } else {
+ pstmt.setObject(10, rftOptions.getNotpt());
+ }
+ if (rftOptions.getBinary() == null) {
+ pstmt.setObject(11, new Boolean(true));
+ } else {
+ pstmt.setObject(11, rftOptions.getBinary());
+ }
+ if (rftOptions.getSourceSubjectName() == null) {
+ pstmt.setObject(12, "null");
+ } else {
+ pstmt.setObject(12, rftOptions.\
getSourceSubjectName());
+ }
+ if (rftOptions.getDestinationSubjectName() == null) {
+ pstmt.setObject(13, "null");
+ } else {
+ pstmt.setObject(13, rftOptions.\
getDestinationSubjectName());
+ }
+ if (rftOptions.getIgnoreFilePermErr() == null) {
+ pstmt.setObject(14, new Boolean(false));
+ } else {
+ pstmt.setObject(14, rftOptions.\
getIgnoreFilePermErr());
+ }
2070c2127,2128
- query.append("' limit 1");
---
+ query.append("' ");
+ statement.setMaxRows(1);
|

 |

|
运行 RFT 测试工具来验证修改是否有效
可以运行 RFT 测试工具来验证这些修改是否有效。这可以参考 RFT 管理手册中的提示。
致谢
特别感谢 Argonne National Laboratory 的 Ravi Madduri。他是 RFT 的作者,也是本文中介绍的大部分代码的作者。还要感谢 IBM 的 Sneha Varghese 和 Shama Ghulamhussain,他们贡献了 Globus Toolkit 中对早期版本的 Cloudscape 的支持。
参考资料 学习
获得产品和技术
讨论
关于作者  | 
|  | Tom Seelbach 作为一名开发人员、测试人员以及 IBM Grid Toolbox 开发组的负责人,已经具有很多年使用 Globus Toolkit 的经验。在过去的几年中,他是 Tivoli Personalized Services Manager(WebSphere Everyplace Suite 产品的一部分)的负责人。他还为 http://movies.excite.com 开发了电影搜索和个性化设置,并为 http://money.excite.com 开发了内容管理。在娱乐方面,他喜欢踢英式足球,并花了一些时间来耕种农作物,并尽量与自己的妻子和 3 个女儿待在一起。 |
对本文的评价
|  |