【IT168 技术文档】
当企业级计算进入新的SOA世界时,在寻找描述/发布/和发现服务的方面中开始变得越来越重要。基于网络服务的方案不提供自动服务发现而且通常都太繁杂了。现在新的轻量级的开发框架提供了新的轻量级的服务发布方案。
在过去几年中,Spring框架已经成为开发简单、灵活而且容易配置的J2EE应用的事实标准。Spring的核心是IoC法则。根据IoC,应用必须以一个简单JavaBean的集合来开发,然后用一个轻量级的IoC容器来绑定他们并设置相关的依赖关系。
在Spring中,容器通过一系列bean定义也配置,典型的是用XML文件方式:
<bean id="MyServiceBean" class="mypackage.MyServiceImpl">
<property name="otherService" ref="OtherServiceBean"/>
</bean>
当客户端代码需要请求时MyService,你只要如下编码:
MyServiceInterface service = (MyServiceInterface)context.getBean("MyServiceBean");
service.doSomething();
除了IoC之外,Spring提供了几百种其他服务,代码约定,而且通过回调标准API来简化开发典型的服务端应用。无论应用使用重量级的J2EE API如EJB/JMS/JMX或者使用流行的MVC框架来构建网络接口,Spring都提供了简化的效果。
随着Spring框架的成熟,越来越多的人使用他作为大型企业级项目的基础。Spring已经通过了伸缩性开发的测试而且可以作为组件粘合剂来联结复杂的分布式系统。
任何企业级应用都由各种组件组成:如联结以前的系统和ERP系统,第三方系统,网面/表示层/持久导等等。通常一个电子商务站点都是由简单的网页应用逐渐深化成包含上百个子应用和子系统的大项目,而且要面对其中的复杂性会阻碍以后的发展。通常的解决方案是将集成电路般的应用分解成一些粗纹理的服务并将其发布到网络中。
不管应用是被设计成作为分散服务的集成点或者已经集成为一体,管理所有分布式组件和其配置的任务通常都是耗时和代价高的。但如果你使用了Spring作为应用组件的开发平台,那么你就可以使用Spring的远程服务通过一系列的协议来将组件暴露给远程的客户端。通过Spring,可以使你的分布式应用就如修改一些配置文件那么简单。
在Spring中最简单的java-to-java的远程通讯方案是使用HTTP远程服务。例如,在web.xml中注册了Spring的分发服务件后,下面的上下文片断就可以将MyService作为公共接口使用了:
<bean name="/MyRemoteService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="MyServiceBean"/>
<property name="serviceInterface" value="mypackage.MyServiceInterface"/>
</bean>
如你所见,实际的服务被注入到bean的定义中因此可以被远程调用。在客户端,上下文定义如下:
<bean id="MyServiceBean"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl"
value="http://somehost:8080/webapp-context/some-mapping-for-spring-servlet/MyRemoteService" />
<property name="serviceInterface"
value="mypackage.MyServiceInterface" />
</bean>
通过Spring的魔法,客户端代码不需要改变,而远程方法的激活就像以前的本地调用一样。除了HTTP远程服务外,Spring还支持其他的远程协议,如基于HTTP的解决方案(Web services, Hessian, and Burlap)和重量级的如RMI。
配置和部署基于URL的远程服务
通过基于HTTP远程服务来部署应用服务有几个明显的优点,其中一个是相对于RMI或EJB方案,你不需要担心更多的配置问题。任何尝试过使用JNDI配置(来自不同厂家的J2EE容器或者同一厂家容器的不同版本的负载均衡及群集)的人都这样认为。
URL是无格式的文本串,而这是最方便的。但同时,通过URL定义服务使得定义有些脆弱。在前面章节列举的URL的不同部分都会按照自己的方式进行变化。网络拓朴变化,负载均衡服务器代替普通服务器,应用被布署到不同机器的不同容器中,网络防火墙间的商品被打开或关闭等等。
此外,这些不稳定的URL必须被存储在每一个可能访问服务的客户端的Spring上下文文件中。当变化发生时,所有的客户端必须更新。还有从开发阶段到产品阶段的服务进程,指向服务的URL必须反映服务所在的环境。
最后我们到达了问题的关键:Spring的暴露各部分受管理的bean作为远程访问服务的能力是非常棒的。甚至在我们需要定义一个服务为服务名时,对客户端隐藏所有有关服务定位的问题。
自动发现和容错的缓存服务
这个问题最简单解决方法是使用某些命名服务来动态实时的转换服务名与服务位置。实际上,我只需要构建一次这样的系统通过使用 JmDNS类库注册Spring远程服务在Zeroconf命名空间中。
基于DNS方案的问题在于更新服务定义是不可能做到实时或事务的。一个失败的服务器在各类超时前还是出现在服务列表中。而我们需要的是快速发布并更新URL列表来实现服务并在整个网络中同步的表现所有变化。
满足这些需求的系统才是可用的。这包含各种分布式缓存的实现。对Java开发人员来说最简单的想像缓存的方式是认为缓存是一个java.util.Map接口的实现。你可以通过键值来放入一引起对象,然后你可以用同一键值取得这个对象。一个分布式缓存系统需要确保相同的键/值映射会存在于每一个参与这个缓存的服务器中的相同Map中并且步伐一致的更新缓存。
一个好的分布式缓存可以解决我们的问题。我们在实现了服务的网络中关联一个服务名和一个或多个URL。然后,我们在分布式缓存中存储name=(URL列表)关联并随着网络状态的变化(服务器的加入/移除/当机等)而相应更新。客户端访问参与分布式缓存的服务就像访问私有的服务一样。
作为附加的奖励,我们会在这里介绍一个简单的负载均衡/容错的解决方案。如果客户端知道一个服务与几个服务URL关联,他可以随机地使用其中的一个并且通过为这些URL服务的几个服务来提供自然的但也有效的负载均衡。而且,在一个远程调用失败时,客户端简单地标识那个URL不可用并且使用下一个。因为服务URL列表存储在分布式缓存中,服务器A不可用的情况也会立刻通知给别的客户端。
分布式缓存在常规的J2EE应用中非常有用,是群集服务的基础。例如,如果你有一个分布式的群集应用,分布式缓存可以在你的群集成员中提供会话复制。虽然这种方式提供了高可用性,但也存在严重的瓶颈。会话数据变化的很快,更新所有群集成员和容错的代价非常高。带有会话复制的群集应用效率通常比基于负载均衡的非会话复制的方案低很多。
在我们的案例中使用分布式缓存是因为缓存的数据很少。相对于通常有上千会话对象的分布式系统来说,我们只有少量的服务列表和对应其实现的URL。此外,我们的列表更新并不频繁。使用这样一个小列表的分布式缓存可以服务于大量的服务器和客户端。
在本文的剩余部分,我们来看一下“服务描述缓存算法”的实际实现。