在当今应用架构里,分布式和应用与服务之间的通信都是核心思想。想要从分布式中获益,你必须牢牢记住几条基本的原则,否则你可能会很容易遇到性能和扩展性问题。在开发阶段这些问题不会经常出现,但当你进行负载测试或产品化的时候,你可能会意识到你选择的软件架构不能满足性能和扩展性需求。在这篇文章中,我们重点关注构建分布式应用需要记住的一些关键点。
分布式需要应用之间进行交互。范围包括从大规模集群架构上简单的点到点的交互,到动态的面向服务或基于服务的架构。跨系统边界的通信也是提高软件系统扩展性和可用性的关键。如今软件架构已把分布式作为一个核心的必要的概念。Java平台成为了核心的角色,因为它的分布式、有很好的API和产品支持这些特点。应用场景从像SAP这样在标准软件上的系统集成,到内部或外部的服务集成。SOA提供这样的方法,使服务和应用变的灵活和可重用,可以对新的市场需求很快的做响应。另外,像使用网格计算,虚拟机和多核刀片机的趋势,导致越来越多的集群应用的出现。这主要是由于追求高可扩展性和高可用性驱动的。而且云计算的发展趋势表明,分布式平台将来会更加流行。另外,系统正变得希望能更动态的增加其灵活性。例如,在运行时添加应用节点。这些趋势也导致了系统结构变得越来越复杂。对于开发人员来说,则更难理解产品中服务调用是如何实现的了。这种复杂性和缺乏对相应知识的了解,很容易导致资源消耗的增加(CPU,内存,网络)和性能的降低。
面具后的恶魔
如今,远程技术使分布式应用的实现更加简单。底层通信的细节和服务端和客户端的基础结构对开发人员是透明的。现在,如果要把一个Java类暴露为一个服务,有时只需要简单的加一个注解到这个类上。服务也可以被工具生成的代理很容易的访问。如下图所示,但是,这仅仅是冰山的一角。
远程堆栈的核心块是对象的序列化和传输的格式化。通常,应用的开发者不需要知道这些。但是,这也是很多性能问题产生的原因。效率不高的序列化意味着,通过网络传输了很多不需要的数据。复杂对象的显示和大量的数据,在序列化和反序列化期间,导致CPU和内存的使用会很高。底层的基础架构和它的配置对应用的性能有很大的影响。在客户端,主要是连接的管理和底层线程模型。在分布式应用中使用连接的指导方针和数据库的连接很像。建立一个连接需要一定的时间。但这同样要看是什么协议。例如,建立一个HTTPS的连接的开销要大于一个简单的TCP/IP连接。同时,连接又是系统很重要的资源。所以,使用连接池很重要。正确的配置在这里也很关键,因为错误的配置文件给我们带来的坏处要多于好处。线程的模型涉及到请求如何被处理。重要的是请求是被同步还是异步处理。同步通信阻塞一个进程直到收到相应。在异步通信中,当收到响应时会调用一个回调。这就允许这个线程被其他事务使用。在服务端,可用的工作线程数量就是定义的并行处理的最大服务请求数。网络本身也是分布式应用的一个重要组件。网络是比影响性能更加限制其可扩展性的重要的瓶颈资源。这块通常在开发时会被忽视,因为没有调用实际的网络。
远程调用之美在于…
这有很多可以选择,Java提供了非常多的可能性和技术来实现分布式应用。远程技术的选择对应用的架构、性能和扩展性有十分重要的影响。最“老的”的但是几乎是用的最广的远程协议是RMI(如下图)。
RMI是J2EE应用的一个标准协议。像它的名称暗示的一样,设计时就是为了调用远程Java虚拟主机上的对象提供的方法。对象在服务端被暴露出来,这时客户端就可以通过代理调用这个对象。同样的服务端对象被多个线程使用。线程池被RMI基础设施管理。通信通过TCP/IP被处理,并且使用 JRMP或针对RMI的基于IIOP GIOP(CORBA协议)的协议。应用服务端也提供自己的属性协议来优化其性能。如服务端的引用需要管理一样,RMI基础设施也提供了垃圾回收器来管理引用。这个分布式垃圾回收器(DGC)本身也使用RMI协议来管理服务器端的对象生命周期。除了客户端和服务端很强大,RMI还有一些其他的实现。关于 RMI的详细介绍及应用请参考51CTO之前的文章《用RMI实现基于Java的分布式计算》。
RMI只支持同步通信,缺点上面已经讨论过了。另外,不能为数据驱动的服务提供低级缓存,因为它是基于2进制协议的。开发人员和系统架构能够改变基础设施的配置参数来优化性能。JMS是J2EE平台上使用的第二多的协议。如下图:
有别于RMI, JMS是一个异步的协议。通信是基于队列的,以便监听器可以对消息作出反应。JMS不是一种标准的远程调用协议,但是它仍然能够满足服务与服务之间的交互。在SOA中非常重要的很多ESB的实现,就采用基于JMS的中间件来进行服务之间的信息传递。由于JMS是异步的,一些典型的同步问题就可以避免。在很多系统中,高可扩展性的关键在于能够很快的释放资源(像线程)。在很多情况下,异步处理是唯一合适的方法。JMS提供很多不同的传输格式。XML是最通用的消息格式,但二进制格式也是可能的。消息结构的设计是应用架构的一个重要部分,因为它可以直接影响到应用的性能和可扩展性。
基于SOAP的WEB Service(如下图)和其他相关的WS-*也在Java 企业应用领域中变得越来越重要。
设计SOAP是为了替换CORBA,而且一开始就得到了业界的强烈支持。因为WS-I之间的共同努力,不同平台差不多能够很容易的连接起来。SOAP是一种基于XML的RPC协议,所以很容易和浪费带宽联系到一起。
越来越多的基于REST的服务开始取代SOAP。Java中的REST服务在JSR 311中有说明,是基于HTTP所支持的基本操作而设计的。但是,REST不是作为RCP协议,而是面向资源的,为了访问和操作(web)资源而设计的。这两个协议都支持同步通信。这也是底层HTTP协议所要求的。WS-地址对SOAP协议进行扩展,所以它也允许异步服务的实现。REST最大的优点是,能够很容易的通过HTTP代理实现缓存。REST依靠使用HTTP底层协议,无论如何都和用的机制。
可能犯的错
分布式应用的很多地方都可能出现潜在的问题,如图所示:
在客户端,主要的问题在于糟糕的交互设计-太多的服务调用,或者选择了错误的通信模式。同步事务运行时间过长很容易导致性能问题。在通信层,大量的数据和过多的服务调用所产生的高的网络负载是主要问题。在服务端,不适当的服务接口设计和不合适的序列化策略的使用导致性能和扩展性问题。我们下面仔细看下这些问题。
分布式应用的问题起因
通信协议的正确选择主要取决于系统的整体架构和底层的需求。如果你工作在有 mainframe、Java和.NET组件相互交互的特异环境中,用SOAP是行不通的。在纯Java环境中,在JRMP上使用RMI仍是性能最优,可扩展性最好的解决方案,你能够获得开箱即用的编程支持。在很多SOA实现中,SOA和基于Web Service的实现同义而语。所以有越来越多的使用SOAP作为RPC协议的纯Java应用案例出现,尽管采用这样的方法一点有点都没有。调查显示,和 RMI-JRMP相比,经常使用SOAP还是有意义的。除了描述过的标准协议,一些其他的基于XML的和二进制协议也在一些应用中使用。Hessian的性能就不错。另外,还有一些其他编程语言的实现。例如使用spring把POJOs暴露给远程调用使不改变实现就在不同的协议间切换变得相对容易。 Spring 支持RMI, HTTP, Hessian, Burlap, JAX-RPC, JAX-WS 和 JMS。
反模式:饶舌的应用
在搭建分布式应用时,一个核心的原则就是尽量减少远程调用。这些意味着数据序列化的开销,建立连接的开销和附件的网络负载。另外,CPU,内存和网络资源的消耗限制了可扩展性。所以,为远程应用的接口设计一种方法,来确保必要的服务交互数最小是十分重要的。尤其是那些起初是在本地搭建的,然后为了可扩展性原因遭遇了大量服务交互的应用。这些问题大多会在负载测试或产品化时出现,但当本地开发测试时一点问题都没有。可以采用适当的性能管理方法,在开发过程中分析远程行为就可以避免这些问题。下图显示的是一个通过dynaTrace分析一个应用的远程行为的例子 。
基于这个分析,接口能够重新创建,应用逻辑能够重新设计来减少远程调用的次数。可能的方法是,合并几个方法的逻辑为一个,或在几个调用请求周边的对象处,使用数据容器。特定数据的位置也可以帮助减少远程调用,因为在需要的地方数据是可用的。尤其当读数据时,使用cache可以很大程度上提高性能和可扩展性。在软件设计的早期,服务的分发和可能的通信在成为需求或将成为需求时已经考虑到是很重要的。
反模式:大格式消息
当调用远程的服务时,这通常意味着数据会在不同的协议上传输。像XML在SOAP协议上传输或二进制数据在RMI协议上传输。大多数技术传输对象的数据或对象本身。大多数情况下,序列化的发生是在远程实现的底层。序列化的开销和所传输对象的大小相对应。在实际情况下,我们进行序列化的开销要占到98%。怎么会这样?一个鉴权服务接口需要一个用户对象来授权。这个用户对象不仅有用户名和密码,还有很多属性,关联到其他用例的数据引用。标准的SOAP序列化要创建几千字节的数据消息。这些数据要被服务解析并映射到用户对象结构上,导致大量CPU和内存的消耗。解决方案再明显不过了。接口要重构,只需要用户ID和密码。所以,除了选择正确的远程技术,消息内容的设计对构建好的性能和可扩展性的应用很重要。通常正好符合设计的很好一般对象会带来高性能的回报。
反模式:分布部署
分布式的Java企业级应用会导致一个应用分割成多个服务和一些部署单元部署到一些应用服务上。分布式的有一个组件新的部署包不需要重新部署到其他组件上。另一个可能性是,大量使用的服务能够部署到独立的硬件或被部署多次。有大量部署单元的复杂应用,服务的交互变得越来越难理解。这会导致2个交互频繁的服务被部署到不同的硬件上从而产生很多的远程调用情况出现。在大规模应用中,分析交互的频率和数据大小与部署结构一致是很重要的。很多时候,从分布式部署到本地可以使性能得到很大的提升而不需要损失灵活性和可扩展性。尤其对那些无状态的服务,把它们部署到不同的节点来提升其本地性。
结论
从这些反模式中可以看出,在应用的设计初期阶段就考虑可扩展性是很重要的。它是应用架构的一个关键驱动。在后期提高性能和可扩张性在多数或大多数情况下工作会越困难。对应用产品的详细分析来识别远程调用的频率或大体积数据,优化系统的一致性是不可或缺的。如果你遇到了相似的或不同的问题,请让我知道,我好扩充我的反模式记录。