内容


用高速缓存加速您的 Web 服务

使您的解决方案驶入快车道

Comments

性能概述

在 Web 服务中至少有三个因素(或缺少这些因素)与性能有直接关系:

  • 网络交易时间(Network transaction time):客户机需花多长时间向远程 Web 服务发出一个请求。
  • 处理消息所花费的时间:具体说来,是 XML 解析、任意流程管理、服务的调用 以及最终响应编码。
  • 服务本身执行所用的时间:(请注意这并不包括向代码段发送动态调用的时间。)这最后一条常常是性能不佳的服务中的主要因素。有用的代码要花些时间来执行其功能,这一点容易被遗忘。

Web 服务给日益复杂的功能提供了一个公共接口。虽然没有一个针对底层服务的规范, 但是基本的概念是您编写代码位并将它们部署到网络中。您可以通过多种传输利用这个功能,最普通的是 HTTP。Web 服务的无效率就在这里:许多传输不是以性能为中心的,并且 XML 的包装和解析给予了直接过程调用无法承担的开销。但是,正如阿尔伯特・爱因斯坦(Albert Einstein)曾说过的,时间是相对的。

在因特网的世界中,多数人按照秒来思考时间。多数 Web 站点都在力争比六秒更快的响应时间。 考虑一下您可以使用 Web 服务调用来构建一个 Web 站点(如新闻门户网站),那么 Web 服务的性能甚至需要更快。这是一个挑战。其目标是使半参与的 Web 服务的响应时间大约为三秒。您可以通过查看您已经控制的 Web 服务部分(具有特殊功效的代码位)来达到这个目标。

如今的 Web 高速缓存

如今的 Web 高速缓存系统利用一些指向内容片段的唯一的标识符。 他们利用底层的协议来跟踪数据位(如 HTML 文档或图像)的及时性。虽然 HTTP 传输上的 Web 服务有端点(URL),但是使其唯一的简单对象访问协议(Simple Object Access Protocol(SOAP))信封(envelope)部分是 SOAP 信封中的参数值,如 清单 1 所示。由于 SOAP 是 XML,几个标记可能有许多不同的标签但仍然标注同一个数据。作为一个整体,您可以创建一个与下一个 SOAP 信封不同的 SOAP 信封并同时使它们恰好代表同一个数据。此外,如果您决定在一个 SOAP 信封单独的、唯一的部分的基础上进行高速缓存,那么没什么办法来表明这个查询是否适于这种高速缓存。

开发人员为特定类型的情况和数据调整当前的商业 Web 高速缓存解决方案。而且,即使有解决方案存在,它可能也没有将开发人员所要求的操作考虑在内。虽然有现成的商业工具可以使用,但是运行您自己的高速缓存对象则提供了最好的灵活性、最强的控制和最大的购买能力。

您可以高速缓存什么、在哪里高速缓存?

多数进程都涉及到了计算。计算可以是一个等式或者是能产生一个新值的网络交易。 不管计算是什么,您都应该标识出浪费时间的进程部分。考虑一下报告原子时间(atomic time)的进程吧。如果这个进程需要一个到政府 服务部门的简单网络时间协议(Simple Network Time Protocol(SNTP))连接,那么更加频繁地计算比每几秒钟计算一次浪费时间。完成一个到原子时间服务的连接所需的时间会影响时间读取的精确性。 对于这个示例来说,假设您仅保持十秒的精度。获得当前时间的网络调用是高速缓存的一个完美项,因为您的 10 秒约束意味着数据在至多 10 秒钟之内不会更改。 您仅仅需要大约每 8 秒进行一次新的连接,以尝试对网络等待时间进行补偿。

这种样式的高速缓存是简单的。涉及到两个静态变量:您上次检查时间的时间以及时间本身。您上一次检查的时间作为触发刷新来自时间服务器(timeserver)的时间的变量。这个时间需要在存储时间过期的任何时刻时被刷新。这是在上一次检查的时间戳记基础上被检查的。 显然,如果客户机对时间的请求在所存储的时间过期前进入,您会返回到已被高速缓存的时间。

您还可以为文本转化为语言(text-to-speech)服务生成并高速缓存音频文件。 生成大量数据以代表一个音频文件需要耗费许多资源。这个服务接收字符串并将音频文件作为一个附件返回。您可以存储这个动态生成的音频并将文本表示存储为密钥。

这够简单的了,但是请考虑一下:您将这些已高速缓存的项保存多久以及其他客户机将多久调用一次这个 Web 服务?

第一个问题诉诸了这样一个事实:每一个系统的资源都是有限的。将 100 MB 的音频数据存储在内存中可能使存储量过大。您可以设计一个高速缓存来拥有一个最大的存储量。 它可以将用得最多的条目保存,同时又将最陈旧的和最少用到的条目清除。您可以决定这个高速缓存没有时间限制。

第二个问题突出显示了服务可能被其他客户机很少使用,以至于对任何事物进行高速缓存都没有用处。例如,假设一个想像的 Web 服务的总交易时间为两秒。如果对这个 Web 服务的调用不多(每分钟少于一个),那么可以确定没有必要进行高速缓存。然而,如果负载是每秒 100 个调用,那么可能有正当的理由进行高速缓存。 一个变通方法是使用一个能在需要时存储条目而在过了有效期后清除条目的自清除高速缓存(self-pruning cache)。清除高速缓存对象减少了它的内存量。高速缓存能够依靠实现将常常被高速缓存的对象保存,时间长于不常使用的被高速缓存的对象。

高速缓存的设计常常以其将得到使用的环境为基础。本文既包括了简单的示例,也包括了复杂的示例。本文还考虑到了以下内容:

  • 时间灵敏度和系统资源的谨慎使用
  • 特定的或通用的高速缓存(存储对象或特定的数据类型)
  • 所高速缓存的数据的灵敏度:如果数据是个密码,您可能可以在获得已加速的性能的同时使用单向散列来安全地存储密码

在流中插入一个高速缓存

高速缓存在任意应用程序中都会是一个有用的工具。在 Web 服务中,您可以更加频繁地使用 高速缓存,因为应用程序的本质在许多情况下是重复的和高负荷的查询。 有时您在代码中插入一个高速缓存解决方案是合适的。在其他情况下,当您可以将一个 Web 服务调用的全部结果高速缓存时,在该 Web 服务被调用之前就插入高速缓存检查会比较好。为了演示这后一种方法,假设您正在使用 Apache 的 Axis 中的 Java(TM) 开发环境和 Web 应用程序服务器(如 IBM WebSphere)。 (请注意您 在这种类型的环境中需要 做的每一样工作都在用于 Web 服务的 IBM WebSphere SDK 中有所提供。) 这个练习的目标是将高速缓存与实际的 Web 服务代码分离开来。

图 1. Axis 流体系结构
Axis 流体系结构
Axis 流体系结构

Apache 的第一个 SOAP 实现取决于提供程序的概念。这个代码位将 SOAP 信封的对象和 方法进行解码,实例化本地对象,并返回将会在返回 SOAP 信封中被包装起来的结果。Axis 有一个与提供程序功能相当的部分,不过它通常被称为轴心点(pivot point)。这是因为 Axis 提供了您可以用来定义交易流的体系结构。您通过处理程序定义流。每一个处理程序 都可以对当前传入的和传出的 SOAP 信封进行操作。处理程序提供了将方法和服务作为一个大一些的服务的流的一部分执行的能力,如 图 1 所示。轴心点恰恰是另一个处理程序,但是它暗示了请求成为响应的点。这个轴心点是一个您可以插入高速缓存的地方。

本示例应用程序的高速缓存是一个通用的解决方案。您将把对象设计为一个单子实例 (singleton)(这个设计模式将把这个对象限制在一个单个实例中)并使用合适的检索和存储方法对高速缓存进行操作。高速缓存将这些对象存储一个固定时间并将旋转一个线程来在需要时清除这些项,如 图 2 所示。对于什么可以被高速缓存这个问题只有一个限制 ― 这个项必须是一个对象。

图 2. 自清除高速缓存对象的高级视图
自清除的高速缓存对象的高级视图

因为许多 Apache 项目具有开放源代码性质,因此您可以为 Axis SOAP 实现下载源代码。(在撰写本文时它仍是测试版,但是它最终会是一个受欢迎的针对 Java Web 服务工作的解决方案。)您将修改 RPCProvider 类(org.apache.axis.providers.java.RPCProvider.java)。许多提供程序都支持多种传输。虽然您可以编写自己的类,但是您可以采用简单的途径,利用 Axis 开发人员已经做的所有艰辛劳动。您完全可以拷贝由 Apache 员工开发的 RPCProvider 并添加一些代码来以一个 RPCCachingProvider 结束。为了高速缓存一个来自 Web 服务的响应,您需要生成一个用来对它进行索引的唯一的密钥。在将被调用的对象和 SOAP 信封中的变元的基础上(如 清单 1 所示),您可以生成这样一个密钥。具体说来,您需要确定服务、方法和变元。

清单 1. 回送服务样本 SOAP 信封
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
 soapenv:encodingStyle=
  http://schemas.xmlsoap.org/soap/encoding/
 xmlns:soapenv=
  http://schemas.xmlsoap.org/soap/envelope/
 xmlns:xsd=http://www.w3.org/2001/XMLSchema
 xmlns:xsi=
  http://www.w3.org/2001/XMLSchema-instance
 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/
  soap/encoding/">
  
 <soapenv:Body>
  <ns1:echo xmlns:ns1="urn:Echo">
    <arg0 xsi:type=
    "xsd:string">Helloworld!</arg0>
  </ns1:echo>
 </soapenv:Body>
</soapenv:Envelope>

请注意这个高速缓存提供程序假定变元有唯一的字符串表示,但不是每一个调用都有唯一的字符串表示。例如,当一个字节数组作为字符串被检索时,它将看起来有些像 [B@4f25045a。每一个字节数组看起来都是不同的,高速缓存将假定每一个数组都是唯一的,这导致了高速缓存没有有效工作。虽然有许多解决这个问题的方法,但是本文使密钥生成简单。一旦您有了一个唯一的密钥,请检查一下看看在高速缓存中是否有这个项。 如果有这个项,那么就返回已存储的响应。在这种情况下,您可以在高速缓存一个 Web 服务调用产生的响应的地方高速缓存 SOAP 体,如 清单 2中的 soapenv:Body 标记描述的那样。您可以决定更新高速缓存项的时间来使其时间更久些。但是,如果高速缓存没有一个匹配的项,您应当继续执行这个调用并存储所产生的响应片段。您可以指定所高速缓存的项目每次保留 30 秒,并在每次被请求时刷新,如 清单 3所示。

清单 2. 样本 SOAP 返回信封
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv=
 "http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd=
     "http://www.w3.org/2001/XMLSchema"
    xmlns:xsi=
     "http://www.w3.org/2001/XMLSchema-
       instance">
 <soapenv:Body>
  <ns1:echoResponse
    soapenv:encodingStyle=
    http://schemas.xmlsoap.org/soap/encoding/
    xmlns:ns1="urn:Echo">
  <echoReturn xsi:type="xsd:string">
    Hello world!</echoReturn>
  </ns1:echoResponse>
 </soapenv:Body>
</soapenv:Envelope>
清单 3. 高速缓存提供程序代码摘录
// Build key for cache.
Stringkey=
  operation.getMethod().toString();
for(inti=0;i<argValues.length;i++){
  key+="::"+argValues[i];
}
// Get the cache instance.
// Make sure DEBUG is off so you don't fill
// up your logs. Check to see if there
// is an item for the key you created above.
Cache cache = Cache.getInstance();
cache.setDEBUG(false);
RPCElement resBody=
  (RPCElement) cache.getCachedItem(key);
  
// If resBody is null, you want to cache it.
if (resBody == null) {
  // Call the service and
  // build the response body.
}
// In this case, cache the item every
// time to keep it around longer.
cache.addCachedItem(key, 30000, resBody);
resEnv.addBodyElement(resBody);

在轴心点实现高速缓存系统将其与服务代码分离开来。奇妙之处在于服务的部署。 不要标识缺省的提供程序(java:RPC),而是将轴心点指定为处理程序并向指向您的新的高速缓存提供程序的服务添加一个参数,如 清单 4 所示。 这个体系结构让您在部署时决定是否使用高速缓存。您甚至还可以用一个服务参数为高速缓存项具体化生存时间。Axis 提供了所有的这个功能和灵活性。您可以在旧的 Apache SOAP 实现中完成同一个提供程序概念,但是为了访问方法和参数值,您要多做一点儿工作。

清单 4. 一个回送服务的样本部署描述符
<service name="urn:Echo" provider="Handler">
  <parameter name="handlerClass"
    value="com.ibm.webahead.providers.
    java.RPCCachingProvider"/>
  <parameter name=
    "allowedMethods" value="echo"/>
  <parameter name="className" value=
    "com.ibm.webahead.services.Echo"/>
  <parameter name="ttl" value="30000"/>
</service>

使用这个解决方案

既然您已经探索了高速缓存系统并且创建了一个通用的高速缓存对象,您可以学习如何在其他的 Web 服务解决方案中集成高速缓存了。虽然您是在流方案中实现这个通用的高速缓存对象,但是您也可以在其他场合使用它。如果不将代码用于工作中,那么即使有它们也没用。

受保护的服务

这第一个示例研究了在访问 Web 服务之前需要进行认证以帮助限制访问和跟踪使用情况的需求。 有时您需要检查一个用户连接到公司 LDAP 目录或数据库的认证凭证,对此进行高速缓存的操作就是这样的事务。

这个示例使用了一个固定大小的高速缓存,如 1 MB 的存储器或 100 个高速缓存项。 它使用用户标识作为密钥,其值为作为 MD5 散列存储的密码。这确保了安全并同时仍能可以根据所高速缓存项而验证未来的密码。如果认证失败,您可以除去这个标识。最后,为了安全起见,请考虑一下将高速缓存超时,因为密码更改和标识会被取消。

第一次您检查用户的凭证时,在成功登录时请将它们存储在高速缓存中。如果凭证经证明是无效的,那么便会返回错误。对这个服务的后继调用可以获得密码认证的安全性,而实际上不一定会导致数据库连接或公司 LDAP 目录查找的开销。虽然这看起来可能并没有节省多少开销,但如果时间很长而且负载很大时,这样就能够节省不少的开销。

使高速缓存抵御重播攻击(replay attacks)

有时,您可能发现您的 Web 服务是个精巧的系统中的一部分。在这第二个示例中,系统需要一台客户机来首先获得一个已被认证的令牌并将这个令牌给后继服务。为了进一步加强系统的安全,令牌只能被使用一次。使 Web 服务能够处理哪些令牌先前已经使用过就可以实施这个只用一次模型(use once model)。它使令牌避免了恶意提交以在一个资源被使用一次后获得对它的访问。这个方案利用了一些在 Axis 安全性演示和 IBM Web Services ToolKit 早期版本中的数字签名的概念。在那些示例中,数字签名在 SOAP 信封的基础上被创建。可以应用同样的验证测试。

例如,您可以生成一个特别的有时间戳的认证令牌(这个令牌已被数字签名以防止在传输过程中篡改)。在访问第二个服务时,您可以验证第一个服务是始发者(originator)且这个令牌具有完整性。这提供了一个使许多 Web 服务解决方案安全的方法;一个对象级别的高速缓存能使您迅速且简单地完成。

在这个实例中,高速缓存系统是自清除种类的。您仅需要一个通过其密钥存储项的方法。 您可以将生存时间设置为比原先的令牌请求时间的有效期长。这个高速缓存便是自清除的,因此您维护了健康的资源使用。

当接收到一个请求时,您可以检查这个项是不是不在高速缓存中。 如果它在高速缓存中,则抛出一个错误。如果它不在高速缓存中,您知道这是一个有效的请求, 并将这个项处理并存储在高速缓存中。当包已经超时并从高速缓存中被清除时,将错误消息从先前已被处理更改为令牌失效。使用这个示例中的高速缓存能够加速性能,因为您可以在提交前决定是否处理这个请求。

结束语

使您的 Web 服务在高度紧张的环境中顺利执行对于技术的存活非常重要。从短期来看,您将看到许多 Web 高速缓存专家都重新设计他们的工具和解决方案以变得更加通晓 Web 服务。即使在他们设计的进候,有时也会在服务级别智能使用高速缓存中获益。随着底层 SOAP 实现的成熟,您将开始看到设计模式参考。这些参考将会讲述如何构建快速的和可伸缩的 Web 服务解决方案。到那个时候,运行您自己的解决方案吧。即使在联网世界到来的时候,有时也有使用定制的高速缓存更好执行的情况。随着 Web 服务越来越受到欢迎,人们对它的要求也会增加。当您构建这个分布式基础结构时,请思考一下速度。当您通过重复使用先前的计算而不是重复底层的进程来提取代码时,您便赢得了时间。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=22273
ArticleTitle=用高速缓存加速您的 Web 服务
publish-date=06012003