Posts Tagged ‘经验’

企业应用集成–java

April 28th, 2009

java企业应用最终往往运行在某种应用服务器中,比如tomcat,jboss等。部署的单元是war, jar 或者 ear文件,开始的时候,我们可能会将多个应用放到一个单元里来开发管理和部署。随着应用的增多,为了能够让每个应用能够独立的运行不受干扰,也为了开发工作能很好的安排,往往会将各应用分开,于是会有多个war, jar,或ear文件。试想一下,假设你有订单系统,产品系统,客服系统,等,如果这些应用都被放在同一个部署单元里,当需要更新客服系统时,会短暂的导致订单系统不能正常工作,而这种情况往往是不能被接受的。另外,如果客服系统非常繁忙,由于部署在同一台物理服务器上,共享硬件资源,订单系统会受到干扰,而这也不是我们想要的。将单独的应用单独打包,单独部署成为必然。那么就面临着让这些应用通信,将它们集成的问题。


与java和php之间集成相比,java与java之间的集成,不仅可以使用共享数据库,soap,或基于http的简单的文档交换,还可以使用jms,rmi等多种方案。其中jms是j2ee众多的标准中最为成功的协议之一,很少有人针对它发表过不满意见。我们在工作中就采用了jms。和http相比,它有如下优势:

为java量身定做的

jms不仅可以传输普通的字符串,还可以直接传输java对象,并和java的其它服务结合起来,接口api也符合java的使用习惯。如果用http,我们还得要考虑如何把对象序列化等问题,比较麻烦。

使用长连接

http主要用在客户端和服务器交互上,为了能让服务器支持大量的客户端,http往往都以一种无状态和短连接的方式工作。而jms主要用在java应用之间的交互,不需要支持那么多的客户端,用长连接更加适合。典型的http服务器,可能同时要给数千数万人提供服务,如果服务器和每个客户端保持一个连接,那对服务器来说,显然是一个非常大的负担。所以,通常客户端和http的一次交互完成后,连接就断开,而下次访问时,还得要重新建立连接。jms服务器面对的客户端一般只有几个,至多几十个,和每个客户端保持连接的开销是可以接受的。典型的企业可能有如下系统:订单系统,产品系统,结算系统,等等,这些系统,每个都和集中的jms服务器保持一个连接,总连接数也是很少的,所以可以保持长连接。这客户端和jms服务器通信的时候,不必每次都建立连接,减少了网络操作,提升了性能。

异步

http操作的过程是同步的,如果要使用异步的方式工作,需要程序员再做些编码工作。而jms天生就以异步的方式来工作的,不必做过多的编码。消息的生产者一旦把消息发送出去,就可以立刻返回不必等待消息的消费结果,后续的消息消费可以自动的异步执行。将不必要立刻执行的动作异步执行,可以大大降低系统的响应时间。

可靠的消息传递

在使用http通信的过程中,如果发生了网络异常,是否要做重发,以及如何重发,会是一件很痛苦的事情。jms提供了persitent的传递模式,在这种模式时,客户端代理在发送消息之前,会先把消息持久,如果发生网络异常,客户端代理会自动处理重发,而不需要应用程序关心。jms消息的唯一编号,可以有效的阻止一个消息被重复处理的问题。

在java应用集成时,jms比http更适合。成熟的jms服务器软件很多,除了ibm的mq,jboss mq, apache activemq 都是很不错的选择。

  • Share/Bookmark

企业应用集成–java与php

April 23rd, 2009

随着企业的发展,企业的业务种类越来越多,业务量越来越大,越来越复杂,部署在单一的服务器上的单一的应用很难再满足业务系统在可靠性,性能,易于维护和扩展等很多方面的需求。进而把企业系统拆分成很多的独立应用,部署在多台服务器上,成为必须。拆分开的多个应用并不能独立的完成所有的业务,应用间必然需要集成。用什么样的方式把各应用集成,不仅能满足系统的功能需要,还能做到易于维护,易于扩展,高性能,高可靠,就成为了一个难题。

公司最开始用php做所有的内部(指面向公司内部职员)和外部的应用(外部指面向internet用户的),之后公司开始采用java来逐步代替php来开发内部应用。所以,我们java程序不但面临着如何集成java内部的各个应用的问题,还面临着如何和php集成的问题。我们使用用过以下方法和php集成:

数据库共享:

java和php使用相同的数据库,为了减少混乱,同一张表,只能由java或php其中一方负责写。这种方法使用起来直观,易用,但还是有很多的问题。
首先,java和php对数据库的一些行为是不一致的。mysql数据库的datatime类型,再插入不正确的数据时,mysql会在里面插入”0000-00-00 00:00:00″, php在读取这样的日期时没有问题,反正,他是字符串。但是java这边却会出错,应为jdbc会默认的将它转化成java.sql.Date类型,而在转化的是时候会发生异常。更大的阻力来自于php和java两个团队在使用数据库习惯上的差异。
其次,数据库共享功能不够强。比如,php完成某个业务时,需要及时通知java,但是仅通过数据库共享不好做,除非java对数据库轮询,但这样做显然很难在及时性和性能两者之间找到平衡。
再次,可扩展性差,但需要做出变更时,变更数据库显然是一件更加痛苦的事。这不仅要处理遗留的数据,还要调节两边的数据库访问程序。
所以,正是后续的项目里就没有再使用过这一方案。

SOAP:

按理说soap这种语言中立又规范化的技术应该非常适合于这种场景,但是在使用过程中还是会出现很多的问题。
首先,php和java两边对soap的理解并不一样。Java生成的布尔值在soap的xml里并表示为true或则false,但是在php看来只要不是0,都是true,所以”false”也是true。也许是php那边所使用的soap支持程序有问题,但是这个问题确实给我们带来了很多麻烦。还有一个要命的问题,就是字符集的问题,php以前一直使用gb2312和gb18030,但是soap的xml并不支持gb18030里的某些奇特字符,一旦遇到这样的字符,xml解析就失败。后来,不得不把可能出现此类字符的内容用base64编码。顺便发一下牢骚,这些奇怪的字符网网都来自于那些爱使用脑残体火星文的新新人类,这些人简直就是弱智!

基于http的简单文档交互:

这种方法很简单,就是通过http post个xml或其它文档来实现交互。这种方式就是原始的使用http的soap,只是soap后来加了一大堆的标准(ws-*),在这些标准对我们来说没有意义,而php和java两边对标准的支持不兼容的时候,soap就成了鸡肋。后来,我们就干脆抛弃了soap,而通过我们自己的程序来发送和接收我们自定义的没有歧义的xml。目前为止,这是两边都乐意使用且很少出问题的方式。这种方式需要两边很好的配合,需要规范化的文档,否则以后只会变得混乱。

我们也曾想过使用基于java的php实现,来达到让java和php在语言级别集成,这种方法对于php遗留系统来说风险太大,而且php程序员面并不乐意生活在java的虚拟机内。也许未来这能成为一个成熟的方案。

正因为使用不同技术的应用之间的集成存在很多的问题,在实际项目中应该尽量避免。

  • Share/Bookmark

JavaEE组件的并发与无状态

January 5th, 2009

并发与线程安全

串行运行时正确的程序在并发运行时可能会出错,这是由于并发运行的多个任务(进程或线程)之间共享了变量。在Java EE环境下很多的组件最终都是在多线程环境下并发运行的,应该牢记住这一点,避免发生诡异的错误。在上个项目中,我们的程序就出现过诡异的错误,在并发量小的测试环境下,它很少出现,但是随着并发量的增大,它出现的次数增多。经查,发现如下类似代码:

    public class SomeServlet extends HttpServlet {
        private SomeType someVariable;

        public void doPost(HttpServletRequest req, HttpServletRespons rsp) {
            method1();
            method2();
        }

        void method1() {
            ...
            someVariable = someValue;
            ...
        }

        void method2() {
            ...
            someVariable = someValue;
            ...
        }
    }

method1() 和method2()都访问到了someVariable,而且程序员的本意是想在method2()使用由method1()产生的 someVariable,但是Servlet可能被多个线程共享,上面的代码不能正常工作。当Servlet容器用thread1和thread2两个线程来服务两个请求request1和request2,而thread1和thread2使用了同一个SomeServlet对象,如果JVM在 SomeServlet在执行到method1()后和method2()前时发生线程间的切换,那么就很可能导致出错,比如:thread1.method1()->thread2.method1()->thread1.method2()->thread2.method2()。实例变量和类变量都是在线程间共享的,而方法内的局部变量和参数是不会被共享的,所以要处理这个问题,最简单的方法是通过方法参数来传递 someVariable,而不是通过实例变量。所以这段程序改成这样就可以了:

    public class SomeServlet extends HttpServlet {

        public void doPost(HttpServletRequest req, HttpServletRespons rsp) {
            SomeType someVariable = method1();
            method2(someVariable );
        }

        SomeType someVariablemethod1() {
            ...
            return someValue;
        }

        void method2(SomeType someVariable) {
            ...
            someVariable = someValue;
            ...
        }
    }

通过synchronized等并发访问控制机制也可以解决这个问题,但是我觉得上面的方式是最直观的。也许有人会说后面的代码比前面的看起来要难看,不够面向对象。这里不讨论怎样才是面向对象,或者面向对象好不好。总之后者是正确的,前者是错误的,没有正确性的程序是没有意义的。

被过线程共享而不会出错的对象,被称之为线程安全的。显然Servlet不是线程安全的,而且还有很多组件也不是线程安全的,比如HttpSession。在上个项目中,我们使用JAXB来处理XML,每次使用时都创建一个JAXBContext对象,后来发现创建JAXBContext的代价是相当的高,于是我们想把它缓存起来在整个应用程序范围内使用,幸好我们使用的JAXBContext的实现是线程安全的,可以放心的被多个线程共享。

有状态与无状态

有状态组件是这样一种组件,组件调用者的返回结果会依赖于这个组件之前或正在受到的调用。无状态的组件则相反,调用者的返回结果只和本次调用相关,所有调用者的调用过程不会影响到其他调用者。举例来说,someComponent组件的getLatestCaller()返回上一次的调用者,这个行为的返回值总会和上一次的调用者相关,这样的组件就是有状态的。假如有个组件math有个方法为add(x,y),返回x+y的值,那么无论之前被哪个调用者调用过,math.add()的返回值只和本次调用的参数相关,这样的组件是无状态的。无状态的组件是线程安全的,因为被别的调用者调用并不会影响到本次的调用结果。

除非有必要,否则应当尽量把你的程序组件实现成无状态的。在spring, seam, ejb里都有无状态的组件。不同的框架在如何实现无状态方面各不相同。最简单的实现方法就是为无状态组件做一个包装,每次调用组件的方法时,都由包装类生成一个新的对象来处理,这样实际上就不再任何的调用者之间共享被调用者的实例,实现了无状态。比如:

    public interface SomeInterface {
        public SomeType doMyBusinness(SomeArg arg);
    }

    public class SomeStatlessComponent implements SomeInterface {
        public SomeType doMyBusinness(SomeArg arg) {
           ...
        }
    }

    public class SomeStatlessComponentWrapper implements SomeInterface {
        public SomeType doMyBusinness(SomeArg arg) {
           return new SomeStatlessComponent().doMyBusinness(arg);
        }
    }

如果实例化组件的代价是昂贵的,用一个对象池缓存组件实例,并在每次从池中取对象时清空对象的状态可能是个更好的办法。其实只要遵循简单的原则,就很容易实现无状态,就是不使用实例变量或类变量,或者只使用只读的实例变量或只读类变量。这样的组件用单例就可以实先无状态。

由于实现上的差别,有些框架根本就不会做过多的努力来保证组件的无状态性,相反他们把责任交给了程序员。作为组件的创作者,如果你确定你要的是无状态的组件,那么只使用只读的实例变量或类变量,是个明智的选择。

  • Share/Bookmark

jboss集群的Barrier mbean是个好东西

December 9th, 2008

在jboss集群时,有一种服务被称为高可用单例服务,意思是指在集群中同时肯定有且只会有一个节点提供这个服务。换句话说这种服务只会在一个节点上启动,并且当这个节点不可用时,集群中会有另一个节点代替它,它是一个具有故障恢复能力的单例服务。要实现这种服务,最简单的方法就是把部署单元,如war, ear, jar,等放到jboss/server/all/deploy-hasingleton/ 目录下。但是放在这个目录下的应用是不能热部署,也不具有farm deploy功能的。还好jboss里有一个很特殊的mbean叫 jboss.ha:service=HASingletonDeployer,type=Barrier,利用它可以很容易的实现高可用单例服务。这个bean很特殊,只有在集群的master节点上它才会被启动,而一旦master节点不再是master,它又会停止。所以一个普通的部署单元,只要依赖上了barrier,就会具有高可用单例服务的能力。下面举一个例子。

现在要实现一个高可用的消息驱动bean,监听远程机器上的消息topic/a。如果只在集群中的一个节点上部署这个MDB,一旦这个节点不可用,消息处理就停止了。为了提高可用性,可以在每个节点上部署这个MDB,但这时同一个消息会被每个MDB收到和处理,这不是我们想要的,因为同一个消息应该只被一个 MDB处理。这个时候只要让这个MDB对barrier依赖,再把它部署在所有节点上,那么就只有主节点上的MDB会运行,并且当主节点完蛋时,其他节点变成主节点时,那个节点上的MDB会自动运行。这样我们就实现了高可用单例消息驱动bean服务。

要让mbean对barrier依赖,只要在它的部署文件里填上一行,例如:

    <server>
        <mbean name="xx" code="xx.xx.xxx">
            <depends>jboss.ha:service=HASingletonDeployer,type=Barrier</depends>
        </mbean>
    </server>

对于war,jar,ear,可以在jboss.xml里添加,具体见jboss文档。对于ejb3,加上下面的annotation就好了:

@org.jboss.annotation.ejb.Depends("jboss.ha:service=HASingletonDeployer,type=Barrier")
  • Share/Bookmark