【IT168 专稿】
根据摩尔定律,自从1958年发明集成电路以来,处理速度和存储容量一直在以每两年翻一倍的速度增长。
但是软件系统的发展速度貌似不可避免地超过了这一切的指数增长速度。SOA的应用越来越广泛,而且随之还有服务之间的XML数据传递方式及增长更为迅速的网络应用。虽然你取得了成功,但是你的系统很可能会马上超载,而且是在你最意想不到的时候。
怎么解决这场危机呢?鉴于企业计算的内存和存储需求的持续增长,软件也需要维持相应的步伐。我们从一开始就要使用正确的方法以取得可控制延迟的线性可扩展性。数据文件和信息的容量也在持续增长,需要更多的处理能力,也使那些在使用前需要先将其具体化的软件显得更为笨重。在某些情况下,这些操作在执行前还需要多个输入。
那些构建极限事务处理(XTP)类应用——比如电话公司呼叫建立和计费、网络游戏、安全交易、风险管理和在线旅游订票服务——的人对这方面应该很有了解。还有更广泛的用例就是网络应用需要根据网络容量的增长而提高,但是后台系统却无法应对这样迅猛的流量。
边界成本
在与客户讨论对SOA进行延迟可控的扩展时,我们经常使用一个称为"边界成本"(Boundary Costs)的术语。为便于理解,请考虑以下情景:由一个网络应用(或数据库、外部业务伙伴、从EDI文档转换而来)生成的XML文档需要经过多个服务的处理,而这个过程由BPEL或ESB进行协调。通常的方法是把XML交给总线,由总线根据处理定义调用相应的服务,并将XML文档作为服务请求负载的一部分传递给服务。需要对数据进行处理的服务将依此对XML进行读取。还有需要与数据库交互的情况。这种方法(如图1)看起来非常简单。
但是,在实际中使用这种方法的时候却会遇到扩展性上的问题。从一个服务穿到另一个服务的成本是多少呢?在调用一个简单的业务过程的时候,这个成本消耗要发生多少次呢?如果XML文档非常大,达到几M的范围、数量成百上千,或者两种情况同时发生呢?
而且考虑到大多IT环境是多平台和多种技术的复合体,因此又给这种情况增加了新的困难。即使处理引擎或服务总线的性能非常出色,服务终端的处理过程仍然会成为瓶颈。最近在一家客户网站上显示,一个原来通过需要15秒处理时间的业务过程,最近在峰值负载时经常超过其SLA协议约定的30秒。在过去两年里,开发人员把大部分时间都用在了优化调整这15个服务的每一处细节,但是最后揪出的黑手却是各服务之间的边界成本。他们进行的一次详细检测显示,这15个服务调用都要花费1到2秒的时间在一个开源网络服务工具包里对XML进行解析处理。这并不是有意污蔑开源网络服务工具包,只是简单地说明一下在终端解析处理XML会增加延迟,并且相加效果非常可观。
如图2所示,所调用的各个服务都需要从XML的在线串行化形式中读取其有效内容并解析为本地Java或.NET对象形式,然后才能被业务逻辑处理。并且,如果还涉及到与数据库的交互,那么还会发生额外的相关匹配对象。最后,还要发生相反的过程,产生与服务请求相应的响应并根据业务过程将其发送到下游服务。
在SOA中,比较常用的处理XML的方式是使用网络服务和XMLBeans。通过XMLBeans可把输入和输出完全具体化然后再生成对象,这样可以最大化可用性和处理性能。内存处理可能包括分类、过滤、合并等操作,而所有这些操作都会增加处理每次调用所需的整体内存容量。这种方式缺乏扩展性,而且无法应用到这个领域的许多场景中。许多产品支持XML流,但这种方式也有局限性,即如果不先存放数据可能就无法进行任何有意义的操作。
那么,能不能设计一种方式,把信息储存在一个可以忽略数据大小和单机处理能力的应用网格中呢?该应用网格可以利用多台机器的组合内存和处理能力来完成一项操作,比如处理一个复杂的公式或对海量数据进行过滤。该应用网格还可将数据的生命周期延长到服务请求周期之外,持续到服务器重启,甚至跨越网络边界。
如果你能将网格数据存储的能力和高效的流处理结合到一起,就能够产生一个处理能力远大于以前的、高度的、可扩展的系统。通过将一些补充技术结合到一起,我们就能取得把计算操作扩展到分布式的计算网络中,减少SOA服务、应用服务器和客户应用等数据消费者的处理和内存需求。我们还消除了数据处理过程中对中间存储数据库的需求。通过应用网格我们还可以传送数据的参数而不是数据本身,这样可以极大地提高通信层的效率并且减少甚至消除边界成本。
本文还有一段显示应用网格中处理大型XML文件的代码示例。在典型的XML文件中通常有一些元素在没有任何预定义限制的情况下重复。通过STAX解析器对XML进行处理,并且用JAXB处理XML与Java对象之间的会话,我们可以将这些重复的元素从XML流中提取出来,然后放到应用网格中作为单独的对象。这样对象便填充到了网格中,并且只需消耗有限的内存资源。填充到网格之后,网格便可以使用构成网格的多台机器对数据进行处理。每个网格成员进行一项操作或过虑,然后将中间结果传递给网格客户端并由其处理成最终结果。
什么是应用网格?
应用网格就是一个基于(应用状态数据)内存存储引擎的水平可扩展的代理。这种方式可以形成一个分布式内存池,便于将其线性扩展到由任何高端而低价的标准硬件组成的异质网格。在应用中使用应用网格的同时还能获得高性能、扩展性和可靠性。
应用利用应用网格的一种方式是使用API水平的接口模拟Java Hashmap、.NET Dictionary或JPA接口。还有一种方式是使用SOA环境中的服务接口。所有应用和服务都将数据放入应用网格,一组共同运转的缓存服务器(以群并行控制的方式)负责对数据对象的更新和备份。
如图3所示,应用网格接收了输入数据的请求并通过高效网络协议传送到拥有原始实例数据的网格节点P。然后原始节点将更新的值复制到第二节点B以进行备份,再把控制权返回给服务。
应用网格在多台机器上以合适的位置透明性储存数据。不管应用网格将数据储存在什么地方,只要有Hash值就可随时读取所存储的数据。这样就不再需要复杂的处理位置依赖性和手动分区的应用逻辑了。如果网格中的一个或更多的节点出现故障,或者由于网络原因而无法进行访问,那么应用网格可以马上做出反应并在其它健康的节点上重新布置数据。而且这个过程无视故障节点所参与的自主更新。在图4中,读取数据时原始节点P出现故障。于是Get()请求马上被导向了备份节点并重新生成原始与备份节点对。
存储在网格中的数据可以是从简单的变量到复杂的对象甚至大型XML文档的任何东西。我们选择把可能是非常大的XML文档分成小块并以Java对象的方式储存在应用网格中。这样我们就能使用Java API平行处理对数据的查询请求。
应用网格支持一系列的操作,包括并行处理查询、事件和事务。对于大型数据集,可能将整个数据集合作为一个操作放入网格,然后网格可以将内容分发到多个原始和备份节点以实现可扩展性。在更高级的应用中,网格甚至可以直接在数据储存节点上并行执行业务逻辑,并且具有数据与逻辑的亲和力,从而保证在与逻辑操作相关的数据存储机器上执行逻辑。
SOA与应用网格
下一代利用应用网格的SOA平台可以提供服务架构所能提供的一般功能,比如服务层的提取、数据转换与路由的中介、多协议支持、适配器等,并将其与应用网格功能结合以无缝实现服务请求负载、共享服务状态数据、服务结果缓存和事件驱动架构(EDA)的内存数据缓存。
那么怎样才能实现这些优点呢?在典型的SOA情景中,一个处理流程中的多个服务可能要与同样的数据进行交互。如果没有网格,那么每次调用服务时都必须为服务提供所需的数据。通过网格,我们实现了一种"索物标签"(Claim Check)的模式。
该模式并不使用数据库来存储消息的有效数据,而是使用应用网格把信息保存在内存中,仅为各个服务传递一个或一串数据的关键字。这意味着虽然从一个服务传递到另一个服务的关键字会根据ESB、处理引擎和传输而变化,但是通常都会包含在服务请求中作为协议头属性的一部分或XML有效负载的验证元素。服务开始具有"网格意识",可以根据需求读取数据并调用对数据集的合并运算。处理完成之后,数据集可以继续保留在内存中以进行高速读取操作,也可以以子集和合适的格式异步写入数据库作为长期存储的关系数据(见图5)。
XML网格示例
1. 场景描述
本文的背景信息即是通过网格存储服务请求负载数据以实现多步业务过程的索物标签模式。但是我们的示例的主要目标是展示如何在网格中存储大型XML文档并对其进行操作(然后才能使用索物标签模式对其进行处理)。
示例场景如下:一个大型XML文档需要被多个服务处理。无需对文档进行反串行化、解析、处理、再串行化整个文档,只是把文档分成小块,转换成Java对象并储存在应用网格中。这个操作将由服务链中的第一个服务进行,或者由第一个服务之前的辅助服务完成。从服务向服务传递的是小得多的XML消息--"索物标签,其中包括读取应用网格中的数据所需的关键字。
此外,并不是只有以序列化的方式执行多个服务的时候这个模式才有用。也可以是每天填充一次但是一直要被各个服务操作或被用户通过门户应用查询的诸如汽车租赁利率或航班数据等参考数据。
2. 拆分XML
我们使用STAX解析器将XML文档分解成多个组成部分。因为STAX开始的时候仍会具体化对象树,所以我们人为地寻找诸多重复元素中的第一个。如果XML在一个称为"项类"的容器节点中含有多个"项",解析器就从"项"开始以避免具体化整个"项类"树。列表1是这个操作的关键部分。
列表1:通过STAX解析分解XML流并提取重复的元素
EventFilter filter = new EventFilter() {
public boolean accept(XMLEvent event) {
//first off, we need a startElement
if (!event.isStartElement()) return false;
StartElement e = (StartElement)event;
//more importantly, it must be the first "item"
if ((e.getName().getLocalPart()).equals("item"))
return true;
return false;
}
};
然后我们使用JAXB将单个XML元素转换为Java对象。JAXB允许XML的POJO呈现,这使其串行化比其它XML-Java技术更简单。在运行这段代码之前,我们先用Eclipse XJC插件从XML Schema中生成了JAXB类。列表2显示了这个XML流循环的开始部分,其每一次循环都将生成一个JAXB对象。
列表2:通过JAXB调用将XML元素转换为Java 对象
while (xmlfer.peek() != null) {
JAXBElement<Item> o = (JAXBElement<Item>)um.unmarshal(xmler);
if (o.getValue() instanceof Item) {
Item ii = (Item) o.getValue();
…
}
一旦我们有了指向"项"的参数(JAXB对象),我们就把它放到应用网格中。在本例中,我们使用了可以模拟(Java和C++)Java Map API和(.NET)Dictionary接口的Oracle Coherence(见列表3)。
列表3:使用Map API将Java对象存储到应用网格
//put into grid
String itemKey = ii.getPartNum();
itemCache.put(itemKey, ii);
3. 挖掘应用网格潜力
对象存储到网格之后,即可以内存Java、C++或C#对象的方式读取数据。我们还可以利用应用网格的一些高级功能,比如对内存对象数据的并行查询操作、持续查询、并行处理等。
在本例中,我们只对网格中的数据进行一次简单的查询。列表4中的代码即是一个对所有包含"foo"的项的简单处理逻辑。
列表4:通过应用网格执行并行查询
public Object process(Entry en) {
Item i = (Item)en.getValue();
if (i != null) {
i.setComment(i.getComment() + " (modified)");
en.setValue(i);
}
return i;
}
…and to invoke this…
//Create a filter to find items in the grid
Filter theFilter = new LikeFilter("getProductName", "%foo%");
//Pass in a filter and an operation (class)
Map result = itemCache.invokeAll(theFilter, new UpdateComment());
4. 以事件方式执行网格处理逻辑
通过简单的JavaBean监听器模式即可使应用网格以事件的方式运行Java逻辑——在网格中写入或读取数据时即可触发。这很像Java的读写触发器或存储流程。列表5是一个显示网格中的更新值的事件。
列表5:网格中数据更新和提取时即触发事件
public void entryUpdated(MapEvent me) {
Item i = (Item) me.getNewValue();
System.out.println("Updated Item: " + i.getComment());
}
5. 重构XML
对XML文档进行处理的业务过程终于接近了尾声,现在我们要储存网格中的数据,或将其共享给其它应用或服务。如果要把数据存储到关系数据库中,那么后台就会有关系映射操作对象以不参与服务与应用网格之间的实际交互的方式存在。
对于任何索物标签模式的实现来说,都要注意所有相关的服务其目的都是实现这个模式所能带来的优点。最终这个过程还需要与无法或尚未支持索物标签模式的的服务传递信息,因此还需要图5中的"转换和路由"这步来把数据串行化回XML在线格式。虽然在这种情况下还会产生边界成本,但是步骤1、2、3的效率已经提高了很多。
列表6显示的是使用应用网格API通过关键字搜索所有对象和使用JAXB创建XML的技术。这只是一个简单的使用Java创建XML的示例,但是大家可以从中受到启发,使用XSLT样式表,通过对网格的多次查询获得样式表所需的数据。另外,还可以利用流技术重新组成分散的XML,避免完全具体化内存DOM树。
列表6:重组组成XML以转交给外部服务
Filter theFilter = new LikeFilter("getProductName", Constants.PRODUCT_FILTER);
Set filtered = itemCache.keySet(theFilter);
// Create a blank Purchase Order and populate it. The non-cached // parts could come from elsewhere and merged using XSLT
ObjectFactory of = new ObjectFactory();
PurchaseOrderType po = of.createPurchaseOrderType();
po.setItems(of.createItems());
// Loop over items and add to an object
for (Iterator it = filtered.iterator(); it.hasNext();) {
Object key = it.next();
Item i = (Item) itemCache.get(key);
po.getItems().getItem().add(i);
}
总之,应用网格可以极大地提高处理大量数据的SOA应用的效率和可扩展性。使用水平可扩展的应用网格存储、处理服务请求负载,我们能在可计量的延迟范围内对应用进行扩展。而且,通过应用网格的强大功能,我们还能以内存读取速度执行分布式并行查询和更新。数据变大之后,我们只要扩展网格即可适应需求。这让我们通过更少的硬件基础获得了比以往更高的处理能力,而且可以从一开始就打好扩展的基础,而无须每次碰壁时重新拿出应用的设计图纸。