文章
面向服务的架构
作者:William Markito Oliveira
合适的缓存策略可以在很大程度上改善应用程序性能。
2011 年 7 月发布
Oracle Service Bus 11g 提供内置的缓存功能(该功能利用 Oracle Coherence)为许多缓存策略提供一流的支持。本文通过一个示例来介绍如何使用此功能并探讨一个故障切换场景,该示例展示了缓存策略如何提高 Web 服务的速度甚至能够防止生产中断。
本文假定您具备以下方面的知识:
在本案例研究中,我们有一个名为 CustomerService 的简单 Web 服务,它目前存在一些性能问题,对于每个请求,它都需要 9 秒钟才会作出响应。我们通过以下步骤来创建并模拟这个“有问题的”Web 服务:

图 1 新建 Web 服务项目

图 2 新建 XML 模式
Web 服务将采用与模式相同的数据结构。
<element name="customer" type="tns:CustomerType"></element>
<complexType name="CustomerType">
<sequence>
<element name="id" type="int"></element>
<element name="firstname" type="string"></element>
<element name="lastname" type="string"></element>
<element name="address" type="tns:AddressType"></element>
</sequence>
</complexType>
<complexType name="AddressType">
<sequence>
<element name="street" type="string"></element>
<element name="country" type="string"></element>
<element name="city" type="string"></element>
</sequence>
</complexType>
</schema>

Package:com.oracle.examples.service
Name:Customer
import javax.jws.WebMethod;
import javax.jws.WebService;
import com.oracle.examples.entities.AddressType;
import com.oracle.examples.entities.CustomerType;
import com.oracle.examples.entities.ObjectFactory;
@WebService
public class Customer {
@WebMethod
public CustomerType getCustomer(int id) {
ObjectFactory factory = new ObjectFactory();
AddressType addressType = factory.createAddressType();
CustomerType customerType = factory.createCustomerType();
switch (id) {
case 1:
addressType.setCity("São Paulo");
addressType.setCountry("BRA");
addressType.setStreet("Marginal Pinheiros");
customerType.setId(1);
customerType.setFirstname("João");
customerType.setLastname("Silva");
customerType.setAddress(addressType);
break;
case 2:
addressType.setCity("San Francisco");
addressType.setCountry("USA");
addressType.setStreet("Some address");
customerType.setId(2);
customerType.setFirstname("John");
customerType.setLastname("Lock");
customerType.setAddress(addressType);
break;
default:
break;
}
// forcing slow down
try {
Thread.currentThread().sleep(9000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return customerType;
}
}
该 Web 服务将返回固定值,当然,现实中您应使用 JPA 和/或 Oracle TopLink 在数据库中进行实际的 Customer 搜索。同时,为了在我们的 Web 服务中模拟迟缓行为,这里进行了一个 Thread.sleep() 调用,这将导致该服务在 9 秒钟后才返回。

图 4 有问题的 Web 服务响应
在下一节中,我们将该服务导入 Oracle Service Bus 中,然后将了解服务缓存如何降低这一性能问题的影响。
在此节中,我们将导入先前创建的 Web 服务并在 Service Bus 项目中生成代理服务和业务服务。

图 5 将 WSDL 资源导入 Oracle Service BUS 中

图 6 创建业务服务

图 7 请求的执行存在性能问题

图 8 Web 服务在 9 秒后响应
注意,所有服务调用实际上都需要大约 9 秒钟来执行。有时可能需要更长的时间,如多 1 秒或 2 秒,但从来不会少于 9 秒,因为这是后端 Web 服务的“响应时间”。
在下一节,我们将演示如何集成 Oracle Service Bus 和 Oracle Coherence,以及如何使用进程外缓存策略升级解决方案。
Oracle Service Bus 一直有某种缓存机制,除了可以为代理服务和业务服务类提供对象缓存外,还可以提供 XQuery 和 XML bean 缓存。但自 11g 第 1 版之后,Oracle Service Bus 利用 Oracle Coherence 提供了一种结果缓存的集成解决方案。
Oracle Service Bus 中的结果缓存可用于业务服务。启用结果缓存时,业务服务将不会到达后端服务,响应将来自缓存项。缓存项可能过期,如果过期,下一个服务调用将实际到达后端服务并更新缓存项。

图 9 结果缓存的工作原理
有关 Oracle Service Bus 中结果缓存如何工作的详细信息,请参阅文档。
要为业务服务启用结果缓存,请执行以下步骤:

图 10 启用业务服务结果缓存

图 11 业务服务缓存设置
这将把客户 ID 用作缓存项的唯一标记。另外,将 Expiration Time 设置为 10 minutes。
第一次执行服务时,服务需要 9 秒钟来执行,并会填充缓存项。在 Test Console 中单击 Back,尝试再次执行;服务将直接从 Oracle Service Bus 内运行的内嵌 Oracle Coherence Server 中的缓存项立即显示结果。缓存的 Expiration Time(生存周期)在第 5 步中设为 10 分钟,因此在此期间,您的调用将不会抵达后端 Web 服务。
现在,我们已为这个存在问题的服务提供了一个解决方案。但如果您想了解缓存中发生了什么,或者想了解缓存服务器中有多少缓存项时,该怎么办呢?
Oracle Coherence 为有关缓存服务器的 JMX 和报表提供了很好的 API,这是监视巨大负载生产环境的必经之路。但对于开发环境和其他重要监视情形,可以使用 Coherence Console。
Coherence Console 是 Coherence 自带的一个命令行实用程序,可连接到特定的 Coherence 服务器。这是一个很好的工具,可以检查哪些服务器参与了集群,还可以浏览缓存数据。在本示例中,我们将使用此工具来监视缓存项和 Coherence 服务器。
为了集成 Coherence Console 与 Oracle Service Cache,需要指定以下几项内容:
if [ -f $JAVA_HOME/bin/java ]; then
JAVAEXEC=$JAVA_HOME/bin/java
else
JAVAEXEC=java
fi
OPTS="-Xms64m -Xmx64m
-Dtangosol.coherence.override=$DM/config/osb/coherence/osb-coherence-override.xml
-Dtangosol.coherence.cacheconfig=$DM/config/osb/coherence/osb-coherence-cache-config.xml
-Dtangosol.coherence.distributed.localstorage=false
-Dtangosol.coherence.cluster=OSB-cluster
-Dtangosol.coherence.localhost=localhost
-DOSB.coherence.cluster=OSB-cluster"
CP="$MDW/oracle_common/modules/oracle.coherence_3.6/coherence.jar"
CP=$CP:"$MDW/modules/features/weblogic.server.modules.coherence.server_10.3.4.0.jar"
CP=$CP:"$OSB/lib/osb-coherence-client.jar"
$JAVAEXEC -server -showversion $OPTS -cp $CP com.tangosol.net.CacheFactory $1
2011-06-17 16:14:35.612/0.240 Oracle Coherence 3.6.0.4 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/opt/oracle/osb11114/oracle_common/modules/oracle.coherence_3.6/coherence.jar!/tangosol-coherence.xml"
2011-06-17 16:14:35.621/0.249 Oracle Coherence 3.6.0.4 <Info> (thread=main, member=n/a): Loaded operational overrides from
"file:/opt/oracle/domains/osb11gR1/config/osb/coherence/osb-coherence-override.xml"
2011-06-17 16:14:35.629/0.257 Oracle Coherence 3.6.0.4 <D5> (thread=main, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified
Oracle Coherence Version 3.6.0.4 Build 19111
Grid Edition: Development mode
Copyright (c) 2000, 2010, Oracle and/or its affiliates.All rights reserved.
2011-06-17 16:14:36.127/0.755 Oracle Coherence GE 3.6.0.4 <Warning> (thread=main, member=n/a): Local address "127.0.0.1" is a loopback address;
this cluster node will not connect to nodes located on different machines
2011-06-17 16:14:36.136/0.764 Oracle Coherence GE 3.6.0.4 <D4> (thread=main, member=n/a): TCMP bound to /127.0.0.1:7892 using SystemSocketProvider
2011-06-17 16:14:36.445/1.073 Oracle Coherence GE 3.6.0.4 <Info> (thread=Cluster, member=n/a): This Member(Id=4, Timestamp=2011-06-17 16:14:36.262,
Address=127.0.0.1:7892, MachineId=26733, Location=site:localdomain,machine:localhost,process:13130, Role=CoherenceConsole,
Edition=Grid Edition, Mode=Development, CpuCount=4, SocketCount=2) joined cluster "OSB-cluster" with senior Member(Id=1, Timestamp=2011-06-17 11:00:46.772,
Address=127.0.0.1:7890, MachineId=26733, Location=site:localdomain,machine:localhost,
process:5603, Role=OSB-node, Edition=Grid Edition, Mode=Development, CpuCount=4, SocketCount=2)
2011-06-17 16:14:36.454/1.082 Oracle Coherence GE 3.6.0.4 <D5> (thread=Cluster, member=n/a): Member 1 joined Service Cluster with senior member 1
2011-06-17 16:14:36.454/1.082 Oracle Coherence GE 3.6.0.4 <D5> (thread=Cluster, member=n/a): Member 1 joined Service Management with senior member 1
2011-06-17 16:14:36.454/1.082 Oracle Coherence GE 3.6.0.4 <D5> (thread=Cluster, member=n/a): Member 1 joined Service ORA-OSB-deployments with senior member 1
2011-06-17 16:14:36.457/1.085 Oracle Coherence GE 3.6.0.4 <Info> (thread=main, member=n/a): Started cluster Name=OSB-cluster
WellKnownAddressList(Size=2,
WKA{Address=127.0.0.1, Port=9999}
WKA{Address=127.0.0.1, Port=7890}
)
MasterMemberSet
(
ThisMember=Member(Id=4, Timestamp=2011-06-17 16:14:36.262, Address=127.0.0.1:7892, MachineId=26733,
Location=site:localdomain,machine:localhost,process:13130, Role=CoherenceConsole)
OldestMember=Member(Id=1, Timestamp=2011-06-17 11:00:46.772, Address=127.0.0.1:7890,
MachineId=26733, Location=site:localdomain,machine:localhost,process:5603, Role=OSB-node)
ActualMemberSet=MemberSet(Size=2, BitSetCount=2
Member(Id=1, Timestamp=2011-06-17 11:00:46.772, Address=127.0.0.1:7890, MachineId=26733,
Location=site:localdomain,machine:localhost,process:5603, Role=OSB-node)
Member(Id=4, Timestamp=2011-06-17 16:14:36.262, Address=127.0.0.1:7892, MachineId=26733,
Location=site:localdomain,machine:localhost,process:13130, Role=CoherenceConsole)
)
RecycleMillis=1200000
RecycleSet=MemberSet(Size=0, BitSetCount=0
)
)
TcpRing{Connections=[1]}
IpMonitor{AddressListSize=0}
2011-06-17 16:14:36.500/1.128 Oracle Coherence GE 3.6.0.4 <D5> (thread=Invocation:Management, member=4):
Service Management joined the cluster with senior service member 1
Map (?):
现在已连接到与 Oracle Service Bus 相同的 Oracle Coherence 集群。Coherence Console 提供了各种不同的命令,有关完整列表,请查看文档。
Map (/osb/service/ResultCache): size
0
Map (/osb/service/ResultCache): size
1
Map (/osb/service/ResultCache): list
PipelineResultCacheKey[BusinessService Customer/CustomerBS,getCustomer,1) = owner=BusinessService Customer/CustomerBS,value=[B@58c16b18
现在,我们已经启用了服务缓存,因此具有更好的吞吐性能,并且可以在系统中应对更多用户。但要记住,我们仍在使用进程内策略,这意味着我们将相同的 JVM 内存用于缓存项、WebLogic 服务(如 JDBC 或 JMS),以及 Oracle Service Bus 对象(如转换、代理服务和业务服务)。如果将缓存使用提升至一个很高的量,可能很容易出现内存耗尽的情况,这就会牺牲掉 Enterprise Service Bus 中的所有其他服务,甚至是未使用缓存功能的服务。
要解决这一问题,可以使用外部 Coherence 服务器,即拥有自己的 Coherence 服务器的专用 JVM。该解决方案可以提高整体架构的可伸缩性,支持更复杂的 Coherence 集群基础架构。Coherence 集群配置不在本文讨论范围之内,但本文对于可以针对自己的情形能实现哪些目标以及进行哪些调整提供了思路。
以下是使用 WebLogic 控制台添加新 Coherence 服务器的步骤:

图 12 添加新计算机

稍后我们将设置 Node Manager 脚本以完成此步骤。

图 14 创建一个新的 Coherence 服务器
-Dtangosol.coherence.override=/opt/oracle/domains/osb11gR1/config/osb/coherence/osb-coherence-override.xml
-Dtangosol.coherence.cacheconfig=/opt/oracle/domains/osb11gR1/config/osb/coherence/osb-coherence-cache-config.xml
-Dtangosol.coherence.distributed.localstorage=true
-Dtangosol.coherence.cluster=OSB-cluster
-Dtangosol.coherence.localhost=localhost
-DOSB.coherence.cluster=OSB-cluster
[用您的 Oracle Service Bus 域目录替换 /opt/domains/osb11gR1/]
注意:为了将此 Coherence 服务器用作存储节点,tangosol.coherence.distributed.localstorage 属性需设置为 true。

图 15 Coherence 服务器设置
<socket-address id="2">
<address system-property="OSB.coherence.wka2">127.0.0.1</address>
<port system-property="OSB.coherence.wka2.port">9999</port>
</socket-address>
</well-known-addresses>
<address system-property="OSB.coherence.localhost">127.0.0.1</address>
<port system-property="OSB.coherence.localport">7890</port>
</unicast-listener>
<multicast-listener>
<time-to-live system-property="OSB.coherence.ttl">0</time-to-live>
</multicast-listener>
</cluster-config>
</coherence>
在此我们向 Oracle Coherence 集群添加一个新的套接字地址。由于安全和性能的原因,与 Oracle Service Bus 绑定的 Oracle Coherence 服务器使用 WKA 和 Unicast,因此对于添加到集群的每个新节点,都需要更新此文件,或者在尝试启动其他未列出的节点时确保此处指定的至少一个节点正在运行。在本例中,我们指定一个新的服务器,它监听 localhost 地址 (127.0.0.1) 和端口 9999。
祝贺您,新的 Coherence Server 已启动并且正在运行,该服务器与 Oracle Service Bus 集成,您可以通过 WebLogic 控制台并借助 Node Manager 管理该服务器。下面我们将对此集成进行测试并使用我们的缓存服务器。
现在可以测试我们的进程外策略并再次执行那个有问题的 Web 服务。记住,与 Oracle Service Bus 绑定的内部服务器和外部的 server1 仍然都可以存储缓存数据,因为它们的 tangosol.coherence.distributed.localstorage 属性都设置为 true。
要测试进程外缓存,可以执行以下操作:
MasterMemberSet
(
ThisMember=Member(Id=3, Timestamp=2011-06-20 15:03:22.947, Address=127.0.0.1:7894, MachineId=8417, Location=site:localdomain,machine:localhost,process:11912, Role=CoherenceConsole)
OldestMember=Member(Id=1, Timestamp=2011-06-20 14:34:49.401, Address=127.0.0.1:7890, MachineId=8417, Location=site:localdomain,machine:localhost,process:10716, Role=OSB-node)
ActualMemberSet=MemberSet(Size=3, BitSetCount=2
Member(Id=1, Timestamp=2011-06-20 14:34:49.401, Address=127.0.0.1:7890, MachineId=8417, Location=site:localdomain,machine:localhost,process:10716, Role=OSB-node)
Member(Id=2, Timestamp=2011-06-20 14:47:55.749, Address=127.0.0.1:7892, MachineId=8417, Location=site:localdomain,machine:localhost,process:11562,member:server1, Role=WebLogicWebLogicCacheServer)
Member(Id=3, Timestamp=2011-06-20 15:03:22.947, Address=127.0.0.1:7894, MachineId=8417, Location=site:localdomain,machine:localhost,process:11912, Role=CoherenceConsole)
)
RecycleMillis=1200000
RecycleSet=MemberSet(Size=0, BitSetCount=0
)
)
TcpRing{Connections=[2]}
IpMonitor{AddressListSize=0}
注意,Well Know Address List 中现在有两个地址,Coherence 集群中有三个成员:OSB-node(内部)、server1(外部)和 CoherenceConsole(我们正在使用的控制台)。
我们在上一节中已经看到,外部 Coherence 服务器 (server1) 已负责管理缓存数据存储。但是缓存仍在两个 Coherence 服务器(内部和外部服务器)中进行,而对于生产环境,禁用内部 Coherence 服务器的缓存存储并释放一些 JVM 内存以便用于 JDBC、JMS 或服务处理等其他服务可能是一个好主意。这样更改之后,所有缓存数据将只存储在外部 Coherence 服务器 (server1) 中。
要执行这些设置,请执行以下步骤:
本文展示了两种不同的缓存策略以及对 Oracle Coherence 和 Oracle Service Bus 使用进程外缓存的优点。文中提供了一个 Web 服务示例,该服务在 Oracle Service Bus 中暴露出性能问题,而通过使用结果缓存,明显提高了响应速度。另外,Oracle Coherence Console 与 Oracle Service Bus 集成可作为开发和诊断服务缓存解决方案的另一种方法,可以显示运行中的服务器和缓存数据信息。
进程外缓存可以显著减少 Oracle Service Bus 域的 JVM 内存占用并提高架构的可伸缩性和故障切换能力,对于需要安全伸缩能力的项目,建议采用此方法。