前言
我们做过的大部分系统其实并不是自己从头开始设计和实现的,很多时候是基于现有的基础再做扩展或者和现有的系统集成。尤其是很多企业应用的系统,因为我们定义的很多子系统是为了解决某个特定的问题或者问题域,在后续随着业务的发展和变化对于系统也会有更多的集成要求。于是,集成主要有哪几种方式?他们各有什么特点呢?这些问题就一一的浮现出来。这里主要针对一些原来个人项目中接触过的问题,结合一些前人的经验做一个总结。
企业集成要点
通常来说,我们需要将多个应用系统集成起来是的他们之间能够相互交互。出于系统演化和需求变更的影响,由于系统的差异导致我们集成的时候面临的困难也比较繁杂。很多时候我们最开始在设计某些系统的时候根本就没有考虑到集成的需要。因此在集成的时候主要会考虑一下几个要点:
- 应用耦合度:这一点也和软件工程中的基本设计思想是契合的。即我们要求系统之间的依赖达到最小化,这样当一个系统发生变化的时候也会对另外一个系统产生尽可能小的影响。也就是我们所说的松耦合。
- 侵入性:当进行集成的时候,希望集成的系统和集成功能的代码都尽可能的变动小。
- 技术选择:不同的集成方案需要不同的软硬件,这些牵涉到开发和学习的成本。
- 数据格式:既然系统要集成,从本质上来说就相当于两个系统的通信。那么相互通信的系统就要确定交换的数据信息格式来保证通信的正常进行。我们接触过的SOAP, REST web service, CORBA等都有特定的消息定义标准。
- 数据时间线:集成还有一个需要考虑的就是当一个系统将需要传递数据发送给另外一个系统的时候,他们传送时间要尽可能少。这样可以提升系统整体运行的效率,减少延迟。
- 数据或功能共享:有的应用集成还考虑到功能的集成共享。这种功能的共享带来的好处是使得一个系统提供的功能在另外一个系统看来就好像是调用本地的功能一样。一些典型的应用集成比如说RPC(远程方法调用)就符合这种特征。
- 远程通信:通常我们系统调用是采用同步的方式。可以在一些远程通信的情况下,采用异步的方式也有它的优点,比如说带来系统效率的提升。同时也使得系统设计的复杂度变大。
- 可靠性:我们不仅仅是设计系统集成方案,就是在一些简单系统应用里面也会考虑到,如果某个部分出错了或者失效了该怎么办?有什么办法可以提高可靠性?
OK,有了前面这些要点,我们再结合目前几种主要的集成方式来一一讨论吧。
文件传输(共享)
文件共享传输的方式是一种我们能想到的很简单直观的办法。它的典型交互场景如下:
在这种场景下,我们一个应用产生包含需要提供信息的文件,然后再由另外一个应用来通过访问文件获取信息。在这里,集成部分所做的事情主要是将文件根据应用的不同需要做格式的转换。考虑这种集成方式,我们有几个重要的问题需要考虑:
- 文件的格式:考虑到不同应用系统传递消息的具体样式不一致,A应用产生的文件如果能够给B应用直接使用是最好的了。尤其是如果如果有B应用的原生支持,对于集成来说将大大提高效率。因此,我们一些常见的方法是传递XML或者JSON格式的文本。 当然,在一些UNIX系统里面也有通过纯TXT文本传递信息的。
- 另外一个比较重要的问题就是什么时候产生文件,什么时候处理文件。因为我们一般都需要一定的时间来产生文件,我们不太希望文件产生的太频繁。而且,在一个应用产生文件的时候怎么保证另外一个应用这个时候不去修改它呢?如果文件产生完了怎么通知另外一个应用呢?还有就是,我怎么知道另外一个应用已经处理过我处理的文件了?我们产生的文件会不会有重名的冲突?文件被处理完之后该怎么办?删除它还是重复再应用?这些问题是在消息传输比较频繁时很容易发生的。这些问题的发生会导致两个应用系统之间信息的不同步或者信息的错误,这也是采用纯文件传输的弊端。
- 当然,在一些应用场景之下,文件传输还是有其优点的。在一些信息交换不是很频繁,而且对于信息的及时性要求不太高的情况下,这种方式还是值得考虑的。我们可以采用一些timer job的方式来产生和消费文件。只要保证两者不产生冲突和他们正确的执行顺序。集成的效果还是可以达到的。另外,采用文件传输还有一个优点就是对于集成的系统来说它比较完美的屏蔽了集成的细节。每个系统只要关注符合标准格式的文件内容,具体实现和数据交换他们都不需要关心。
共享数据库
还有一种集成方式也比较常见,就是共享数据库。在很多应用开发的场景下,我们的数据库是相对独立提供服务的一部分。所以对于其他系统的对接也就比较容易,这种集成的方式如下图:
和前面文件共享传输的方案比起来,这种方案有一个相对的优势,就是可以保证数据的一致性。在原来的方案中,如果文件要传输给多个应用的话,我们是没办法保证所有应用的数据是同步而且一致的。有可能有的快有的慢。而在这里,所有的数据都是统一存储在公共的数据库里,也就不存在这样的问题了。对于任何一个系统产生的数据或者变化,另外一个系统也就马上可以看到。
当然,这种方案也有它不足的地方。首先一个问题就是对于多个应用来说,这个共享数据库需要能够适应他们所有的场景。不同的应用考量的点是不一样的,要能适应所有的需求对于数据库这一部分就显得尤其的困难。还有一个就是性能方面的问题,不同的应用可能会同时访问相同的数据导致数据访问冲突,因此也会带来如死锁等问题。
所以说,这种方案出现问题的根源在于用一种统一的数据模型来解决各种不同的应用需求是并不现实的。
RPC(远程过程调用)
远程过程调用的方法在早些年的时候也比较常见。典型的如Java的RMI。典型的应用场景如下:
以典型的java RMI为例,当我们需要访问远程方法的时候,需要定义访问的接口,然后通过相关工具生成skeleton和stub。然后一端通过stub给另外一端发送消息。在应用A本地的代码中访问stub看起来还是和调用本地方法一样,这些细节都由stub给屏蔽了。其他的技术如COM, CORBA, .net Remoting都采用了RPC的思路。
RPC的这种思路能够很好的集成应用开发。当然,由于这种机制也会带来一定的问题,比如说java RMI或者.net remoting。他们都局限于一个平台,好比说我应用A是用java做的,那么如果要和另外一个系统通过RMI集成的话,那个系统也必须是java做的。另外,他们其实还是一种紧耦合。我们RPC调用是用的一种类似于系统api的同步调用,当一端发出调用请求的时候会在那里等待返回的结果。如果另外一个系统出现故障也会对调用方产生很大影响。而且我们用RPC调用的时候默认期望消息是按照发送的顺序给接收方的。但是由于各种环境的影响会使得接收的结果乱序,这样也可能会导致系统执行出现问题。所以从可靠性来说还是存在着一定的不足。
消息队列
看来前面几种集成的方式,我们再来看看消息队列的方式。消息队列的集成方式如下图:
所有应用之间要通信的消息都通过消息队列来传输,由消息队列来保证数据传输的异步性、稳定性等。总的来说,这看起来有点像网络连接结构。所有数据通过一条可靠的链路来进行通信。
那么,这种集成方式有哪些特征呢?
- 更好的应用解耦:像以往采用文件传输或者共享数据库的方式需要知道文件或者数据库在哪里。对于RPC的方式来说甚至要知道对方的IP地址才能进行方法调用。这样的依赖太强烈。而且还对开发运行平台也有依赖。而现在这种方式则是只要双方规定好通信的消息格式,各自都只要发消息给消息队列就可以了。这就好比是两个人写信,一个人只要把要写的内容整理好再交给邮局,剩下的事情他就不用操心,全让邮局给他办了。这样,不管对方是什么语言开发的系统,只要他们采用统一的消息格式,java开发的系统也就能够和C++, .net等平台的系统通信了。
- 消息的可靠性:我们具体发送消息的任务相当于交给了消息队列。所有提交的消息有消息队列里的message router来投递。这有点像网络概念里的路由器一样,根据一个发送方指定的地址并转发到另外一个地方。同时,消息队列也根据不同的需要将消息进行持久化,这样保证消息在投递的过程中不会被丢失。
- 系统可靠性:如果对消息队列和RPC的方式做一个对比,这就好比是生活中打电话和发短信的区别。在打电话的时候,我们是必须期望接电话的对方在电话旁边能够接收响应。而如果接收人不在或者忙的话,打电话的这一方就只能在这里干等。这就是系统不够健壮的地方,一旦另外一方系统出故障,系统就没法正常运作。而且要保证能够正常通信,需要系统双方都同时就位。而发短信的这种消息方式则不然,消息可以准确的送达到对方,如果对方暂时忙消息也会保存在那里。等有空的时候会进行回复。至少保证了有效信息的传递。这种特性也就是保证了系统的异步执行,从某种角度来说也提升了系统性能。
综合上面的这些讨论,消息队列算是一种兼顾了性能、可靠性和松耦合的一种理想集成方式。目前实现消息队列的产品有很多,比如微软的MSMQ, 开源产品ActiveMQ, RabbitMQ, ZeroMQ等。后续的文章还会对消息队列的应用和内部机制做深入的分析。
总结
应用系统集成的方式有很多,最常见的几种有文件传输,数据库共享,远程方法调用以及消息队列。他们在解决某些特定领域的问题时有自己的特长。综合来说,消息队列算是一种比较理想的解决方案。不同事物或者不同领域之间也有很多的相似性,在研究消息队列的时候会发现他们和网络的体系结构思想非常相似。