级别: 中级
王 云 (cdlwangy@cn.ibm.com), 软件工程师, IBM 中国研发中心
2007 年 11 月 15 日
本文介绍了在 Web Service 实施和开发过程中,提高系统效率的一些方法,实践证明,这些方法都是非常有效且易于实现的。针对各种方法的优缺点以及应用场景,文章也进行了相应的讨论。
背景
Web Service 现如今已经成为 SOA 实现标准之一。很多公司已经或者正在参与到 Web Service 项目的实现和部署中。Web Service 的优点在于松散的处理异构系统之间的通信和数据交换,可以随机应变的处理企业各个系统之间的整合问题。但是同时,Web Service 采用 XML 标准进行系统间的数据传输,加大了传输的数据量,尤其是在传输一些具有比较严格结构的数据时,会使得传输效率有所下降。所以,如何提高 Web Service 传输效率成为很多公司进行项目部署时非常关心的问题。
目的
本文介绍了在 Web Service 实施和开发过程中,提高系统效率的一些方法,实践证明,这些方法都是非常有效且易于实现的。不同的方法都有其应用领域和优缺点,我们会分别进行讨论。文章的主要目的在于,提供给读者多种方式的基本解决方案,使得读者在 Web Service 项目部署时,拥有更多的思路。
原因分析
Web Service 是采用 XML 标准进行数据传输的。XML 在传输过程中,会附带很多数据的相关信息,并以标签的形式表现出来。在传输过程中,一些情况下,这些标签会占用一半以上甚至更多的数据传输量,例如,要传输一个表格信息,如下(表格中的人名为虚构的):
表 1. 将要进行传输的表格示例
Name |
City |
Apartment |
Air Wang |
Beijing |
Some Place |
表 1 中的数据在传输过程中,有可能会生成下面的 XML 文件:
清单 1. 对应于表 1 数据所传输的 XML 文件示例
<Heading>
<column> Name</column>
<column>City</column>
<column>Apartment</column>
</Heading>
<Data Grid>
<row>
<column>Air Wang</column>
<column>Beijing</column>
<column>IBM CDL</column>
</row>
</Data Grid>
|
如果上面的表格中还带有格式的信息(比如字体,背景颜色等等)的话,那么相应的 XML 就会更加复杂了。从上述 XML 中我们可以看出,除了数据之外,XML 会附加很多标签信息,这就使得传输的数据量增大,当所需要传输的数据比较多的时候,XML的标签就会带来比较大的效率问题。
解决方案一: 压缩与解压缩
Web Service 在网络中传输的是以 XML 为基础的消息的请求和响应。大量的数据传输会使网络成为瓶颈。一个最直接的解决方案就是对传输的消息进行压缩。对于不同规模的数据量,压缩应该有不同的解决方案,下面分别介绍如下:
1. 对于整个 XML 传输文件进行压缩
数据压缩已经发展了很多年,有很多成熟的技术,算法以及工具包。经常用于对数据压缩的 API 有 gzip 等方式。对文件进行压缩的做法非常简单,就是在发送 XML 之前对 XML 进行压缩,经过压缩以后,再在 XML 接收端对已经压缩的文件进行解压缩。
优点:
该方法的优点在于,使用了成熟的压缩和解压缩技术,当数据量比较大的时候,可以大大提高传输效率。对于纯文本的 XML,压缩可以减少其80%以上的体积。
缺点:
压缩和解压缩虽然可以使得 XML 的体积大大减少,但是其过程却是十分耗费系统资源的。压缩和解压缩往往会具有很大的 CPU 占有率以及内存占有率。对于配置不高的客户端甚至是服务器端,都会造成不小的压力。
应用场景:
该技术应用于网络瓶颈非常严重的情况或是主机配置比较高的情况。
举例:
正如本小节最开始已经介绍的,现在已经有很多成熟的压缩与解压缩的 API 提供给开发人员进行使用,我们选取其中最常用的 gzip 方式举例说明。一般来讲,系统请求 XML 的体积相对较小,没有必要使用压缩和解压缩的方法处理请求 XML。而对于系统响应 XML 来讲,一般都包含大量的数据,导致其体积庞大,需要进行压缩处理。对响应 XML 进行压缩的流程如下:
服务器端数据模型-->序列化操作-->利用 gzip 方式对序列化后的 XML 进行压缩-->返回到客户端-->以 gzip 方式进行解压缩-->对解压缩后的 XML 进行反序列化操作-->客户端数据模型
这里需要说明的一点是,客户端以及服务器端的数据模型需要实现 Serializable 接口。
清单 2. gzip 方式压缩部分实现代码示例(java 实现)
import java.io.*;
import java.util.zip.*;
public class Compress
{
public String gzip(OutputStream pStream)
{
…
try
{
GZIPOutputStream stream = new GZIPOutputStream(pStream);
return stream.toString();
}catch(IOException e){…}
…
return null;
}
…
}
|
在程序将对象模型序列化成 XML 之前,可以使用上面的压缩方法,对数据流进行压缩。部分代码如下:
清单 3. 对象模型序列化后再进行压缩的实现代码示例
public class XMLSerializerHelper
{
…
public static String saveOBJtoString(Object inputOBJ) throws ConverException
{
……
ByteArrayOutputStream outputStream = null;
try{
outputStream = new java.io.ByteArrayOutputStream();
m_serializer.save((IXMLSerializable) inputObject, outputStream);
return Compress.gzip(outputStream);
}catch(Exception e){…}
……
}
…
}
|
解压缩的过程也类似于上述代码。测试表明,采用 gzip 压缩可以减少60%以上的网络所带来的消耗。
2. 对于特定的数据进行特殊的处理
在企业日常的数据传输中,往往大量的数据具有很多共同的特点。数据和数据之间往往具有很多相同的地方,或者说,具有很多重复的地方。例如,在一个以 Web Service 为构架的报表处理系统中,报表往往会含有很多的空数据,或者相同属性和值域的数据,对于这样的情况,可以在代码中对特殊情况进行特殊的处理。我们同样以传输一个表格作为例子,如下:
表 2. 将要传输的含有多个空值的表格示例
Software sold |
Hardware sold |
System sold |
Others |
120 |
- |
- |
- |
- |
- |
90 |
- |
- |
110 |
- |
- |
可以看到,上述表格具有很多的空值,那么在 XML 中完全可以把空值的部分统一处理,这样就能大大减少网络传输的数量,其对应的部分 XML 如下:
清单 4. 对空值进行处理后的简化 XML 示例
……
<NULL Value>(1,2),(1,3),(1,4),(2,1),(2,2),(2,4)(3,1),(3,3),(3,4)</NULL Value>
……
|
优点:
对于重复性的数据来说,该方法可以几十倍甚至上百倍的减少传输的数据量(这取决于数据重复的数量大小),相对于第一种压缩方式,由于只是对固定形式的数据进行处理,所以不会占用很大的 CPU 以及内存。
缺点:
数据的特点不容易把握,能够处理的情况比较简单和单一。对于空值或者某些值重复较多的情况,可以采用本方法。
解决方案二: 减少多次调用,尽量使用一次性的调用方式。
传统的 RPC 调用,在多次使用的时候会产生很大的效率问题。用户在进行每次远程调用的时候都要等待网络传输所耗费的时间。而对于 Web Service 来讲,很多用户仍然将其作为传统的 RPC 来进行调用。每次都调用 Web Service Provider 所提供的函数,造成了效率的极大浪费。Web Service 的一大特点在于,可以在本地进行一次性的设置,再把生成的 XML 统一的发送给服务的另外一端。对于用户来讲,所有的设置工作都是在本地进行的,用户完全感觉不到网络所带来的瓶颈,而在最后的数据传输过程中,多消耗一些时间也是值得的。
应用场景:
对于同用户交互的情况,尽可能使用这样的处理,即减少多次的远程调用,尽量使得程序仅需完成一次调用。举一个简单的例子来说明问题:在一个用户界面上(User Interface),需要进行很多的设置,而这些设置的每一步都是需要进行远程调用的,这样对于用户来说,就会在每一次的设置过程中都等待网络传输所耗费的时间。而对于 Web Service 而言,客户端所有的工作就是生成请求 XML,所有的设置工作都可以统一生成一份 XML 文件,然后将其传送给服务器端,这样一来,用户只用等待一次的数据传输时间(可能相对时间较长,但是用户只用等待一次,还是值得的),而其他的工作都在服务器端进行处理。
优点:
所有数据操作在本地进行,用户在处理数据时不会感到网络所带来的停顿。最终所有操作请求统一发送给服务器端,使得原本多次等待的远程操作只需要一次数据传输就能完成。
举例:
下面的两幅图(图 1 和图 2)是某数据导入向导中的两个步骤,用户通过界面设置数据库以及数据表的信息,从而得到目的数据。
图 1. 数据导入设置向导示例一
图 2. 数据导入设置向导示例二
很多初学者容易进入的一个误区是在数据库选择以后,在程序中直接连接数据库,通过用户提供的用户密码建立数据库连接,然后在后续的每一个步骤中(如图中的设定数据表条件)都进行远程函数调用,以至于用户每一次点击向导中的“下一步”按钮时都会感觉到网络带来的瓶颈,例如停顿感,或者更严重的程序一段时间的没有响应等等。事实上,对于 Web Service 来讲,每一步都可以对请求 XML 进行设置(这种设置是在本地进行的),当所有步骤都完成以后,再将 XML 统一发送给服务器端进行处理。这样一来,用户在每一步之间的操作都是在本地进行的,不会带来多余的网络响应等待时间,使得整个向导的设置工作能够很快的进行。而最后一步完成以后,用户对于网络的一次性等待是相对值得的。在这个例子中,请求XML的部分结构如下:
清单 5. 客户端进行数据导入设置的 XML 示例
……
<Data Retrieving>
<Database>
<Type>Toolbox</Type>
<Pattern>TCP/IP</Pattern>
<UserName>Wang Yun</UserName>
</Password>
</Database>
<Table>
<Condition method=’more than’ value=’table’ >2500</Condition>
<Condition method=’equal to’ value=’table’>Wang Yun</Condition>
<Condition method=’less than’ value=’table’>10 days</Condition>
</Table>
<Data Retrieving>
……
|
系统客户端对 XML 进行设置的部分示例代码如下(将对象模型序列化的过程,仅列出 Database 节点):
清单 6. 对象模型序列化成 XML 示例
public class DataBaseLoginOBJ
{
…
public Element persistToXML(DataObject pOBJ)
{
…
Element dbElement = pOBJ.createElement(“Database”);
Element typeElement = pOBJ.createElement(“Type”);
Element patternElement = pOBJ.createElement(“Pattern”);
Element userElement = pOBJ.createElement(“UserName”);
Element pwdElement = pOBJ.createElement(“Password”);
dbElement.appendChild(typeElement);
dbElement.appendChild(patternElement);
dbElement.appendChild(userElement);
dbElement.appendChild(pwdElement);
…
}
…
}
|
DataObject实现了org.w3c.dom.Document的接口, Element实现了org.w3c.dom.Element接口。在完成了整个请求XML的设置之后,就将其统一传输至服务器端进行处理。
解决方案三: XML 解析器的选择和优化
现在软件领域有很多种类的 XML 解析器,最基本的方式有两种:DOM 和 SAX。对于不同级别的XML文件,应该使用不同的解析器。有关 XML 解析器的介绍,请参考其他相关文档。下面列出他们使用的场景以及优缺点:
表 3. SAX 与 DOM 解析方式的基本比较
种类 |
优点 |
缺点 |
使用场景 |
DOM |
1.XML树在内存中完整存储,因此可以直接修改其数据和结构。 2.可以通过该解析器随时访问XML树中的任何一个节点。 3.DOM解析器的API在使用上也相对比较简单。 |
如果XML文档体积比较大时,将文档读入内存是非常消耗系统资源的。 |
DOM 是用与平台和语言无关的方式表示 XML 文档的官方 W3C 标准。DOM 是以层次结构组织的节点的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能进行任何工作。DOM是基于对象层次结构的。 |
SAX |
SAX 对内存的要求比较低,因为它让开发人员自己来决定所要处理的标签。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX 这种扩展能力得到了更好的体现。 |
用SAX方式进行XML解析时,需要顺序执行,所以很难访问到同一文档中的不同数据。此外,在基于该方式的解析编码过程也相对复杂。 |
对于含有数据量十分巨大,而又不用对文档的所有数据进行遍历或者分析的时候,使用该方法十分有效。该方法不用将整个文档读入内存,而只需读取到程序所需的文档标签处即可。 |
正确的选择XML解析器的种类,对于Web Service系统的效率会有很大的帮助。现在有很多厂家提供了基于这两种类型的很多XML解析器,在选择的时候,应该仔细阅读说明文档,慎重进行选择。另外,往往在一个系统中可以同时使用这两种方式,以达到解析的最高效率。一般来讲,对于请求XML可以采用DOM的解析方式进行解析,而对于响应XML可以使用SAX的方式进行解析。
解决方案四: 简化标签
我们知道,Web Service 解决方案在网络中传输 XML 具有比较复杂的结构。在传输过程中,不仅仅必要的数据被发送和接收,同时也会由于 XML 的结构过于冗杂而附加了更多的信息进行传输。举例如下:
系统客户端从服务器端得到的返回数据的响应 XML 结构如下:
清单 7. 在网络中传输的表格数据对应的 XML
<Cells>
<Heading Cells>
<Column>
<Row> Data </Row>
</Column>
<Column>
<Row/>
</Column>
…
</Heading Cells>
<DataGrid Cells>
…
</DataGrid Cells>
</Cells>
|
由上面的 XML 我们可以看出,每一个返回数值,都至少有 <Column>, </Column>, <Row>, </Row> 来标识它,也就是说,如果我们要传输一个表格,那么表格中每一个数据都要伴随传输相应的这四个标签。假设表格中的每一个数据的字节数为8,那么标签所带来的附加字节就将近为数据的4倍。如果要传输的数据量十分巨大的话,效率自然会降低许多。所以我们可以采用一种十分简单的方式,有效的减少XML所带来的附加字节数,即简化XML标签。可以将上面所说的XML改写成下面的格式:
清单 8. 对应于清单 7 的简化后的 XML
<Cells>
<Heading Cells>
<C>
<R> Data </R>
</C>
<C>
<R/>
</C>
…
</Heading Cells>
<DataGrid Cells>
…
</DataGrid Cells>
</Cells>
|
优点:
测试证明,采用这样的改进方式,可以使得整个系统的效率提高近一倍甚至更多。而且这样的处理方式简单易行,只是修改标签就可以了。
缺点:
使XML自身代表的意义变得不那么容易读懂,弥补的方式是一般会有一个对照表,来说明具体简化后的 XML 文件的含义。
举例:
该方法比较简单直观,这里列出对照表的一个例子如下:
表 4. 简化前后 XML 的对照表及其含义
简化前标签 |
简化后标签 |
含义 |
Row |
R |
表示表格中的行 |
Column |
C |
表示表格中的列 |
... |
... |
... |
类似上面这样的声明表格应该作为文档提供给用户。
应用场景:
这里需要说明一点,XML 能够清晰的表示出整个数据结构,而很多软件开发人员往往希望利用 XML 来观察数据的情况,而标签就是协助他们更好的完成这样的工作。所以,一般来讲,标签需要尽可能的有意义,由于 C 和 R 可以比较明确的表示 Column 和 Row,并且他们在数据传输过程中是重复的最多的(占传输标签总数的80%),所以我们进行了上述的改动。但是对于像 Cells 这样的标签,由于其重复率不是很高,而且我们需要这个标签具有字面上的意义,所以,类似于这样的情况,我们给予保留。
解决方案五: 缓存机制
前面介绍的四种提高效率的方法都是基于 XML 传输的,也就是说,都是在如何提高 XML 传输效率上进行介绍的。而第五种机制对于任何的软件解决方案都适用,而对于 Web Service 解决方案来讲,更是具有锦上添花的作用。缓存机制最通用的一个思想是,将使用次数比较多的数据缓存起来,如果再有相同请求时,就将缓存中的数据返回。这样会减少数据逻辑处理所带来的时间。我们以用户的一次刷新数据的操作为例,介绍一种比较通用的缓存方式,如图:
图 3. 某数据刷新操作的缓存流程
由图中可知,当用户进行刷新操作时,服务器端先检查请求 XML,取出要刷新数据源的 CUID。根据该 CUID,系统检查在缓存中是否存在该 CUID 对应的数据对象,如果有,则取出其刷新状态,并与对应该 CUID 的数据源的刷新状态进行比较。如果比较一致,则直接返回数据给客户端,如果比较不一致,则需要打开数据源进行重新刷新,并将刷新以后的结果集的对象保存在缓存中。如果缓存已经满了,则利用最近最少使用原则,清除缓存中的一部分数据对象。另外,缓存的大小设置是可以通过属性文件进行设置的,在程序中,对缓存进行初始化时,首先读取了属性文件来进行缓存大小的设定。缓存数据的好处在于,在对数据进行多次重复刷新时,系统不需要重新打开数据源(有时候连接并打开数据源是非常消耗时间的),从而提高了系统的效率。
优点:
对于多次的相同方式的数据操作,数据直接从缓存返回,可以很大程度的提高系统效率。
缺点:
编程的工作量比较大,需要测试的部分比较多。
应用场景:
一般的 Web Service 部署程序都可以采用缓存机制的处理。
其他解决方案
对于 Web Service 服务器,各个厂商都提供了很多服务器设置,以提高 Web Service 的性能和效率。具体请参考相关服务器的文档。
小结
对于提高 Web Service 的效率,本文给出了一些解决方案,从各个不同方面对 Web Service 效率的提高进行了讨论。对于不同的应用系统,应该酌情分析系统建立的环境以及系统的使用人群和范围,以最终决定采用何种解决方案。
参考资料
关于作者
|
|
|
作者王云是 IBM DB2 for iSeries 项目组的软件工程师,他对 DB2, SOA 以及商务智能的解决方案有比较深入的理解。他的写作经历大多相关于商务智能以及 SOA 的解决方案
|
|