第1章 分布式Java应用
大型应用通常会拆分为多个子系统来实现,对于Java来说,这些子系统可能部署在同一台机器的多个不同的JVM中,也可能部署在不同的机器上,但这些子系统又不是完全独立的,要相互通信来共同实现业务功能,对于此类Java应用,我们称之为分布式Java应用。
Martin Fowler在《企业应用架构模式》一书中曾经说过:“能不用分布式的情况下就不要用分布式”,当应用变为分布式Java应用时,会很大程度地增加应用实现的技术复杂度,对于分布式Java应用,通常有两种典型的方法来实现。
1. 基于消息方式实现系统间的通信
当系统之间要通信时,就向外发送消息,消息可以是字节流、字节数组,甚至是Java对象,其他系统接收到消息后则进行相应的业务处理。
消息方式的系统间通信,通常基于网络协议来实现,常用的实现系统间通信的协议有:TCP/IP和UDP/IP。
TCP/IP是一种可靠的网络数据传输的协议。TCP/IP要求通信双方首先建立连接,之后再进行数据的传输。TCP/IP负责保证数据传输的可靠性,包括数据的可到达、数据到达的顺序等,但由于TCP/IP需要保证连接及数据传输的可靠,因此可能会牺牲一些性能。
UDP/IP是一种不保证数据一定到达的网络数据传输协议。UDP/IP并不直接给通信的双方建立连接,而是发送到网络上进行传递。由于UDP/IP不建立连接,并且不能保证数据传输的可靠,因此性能上表现相对较好,但可能会出现数据丢失以及数据乱序的现象。
TCP/IP和UDP/IP可用于完成数据的传输,但要完成系统间通信,还需要对数据进行处理。例如读取和写入数据,按照POSIX标准分为同步IO和异步IO两种,其中同步IO中最常用的是BIO(Blocking IO)和NIO(Non-Blocking IO)。
从程序角度而言,BIO就是当发起IO的读或写操作时,均为阻塞方式,只有当程序读到了流或将流写入操作系统后,才会释放资源。
NIO是基于事件驱动思想的,实现上通常采用Reactor模式,从程序角度而言,当发起IO的读或写操作时,是非阻塞的;当Socket有流可读或可写入Socket时,操作系统会相应地通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。对于网络IO而言,主要有连接建立、流读取及流写入三种事件,Linux 2.6以后的版本采用epoll方式来实现NIO。
下面再来看看另一种方式——AIO。AIO为异步IO方式,同样基于事件驱动思想,实现上通常采用Proactor模式。从程序角度而言,和NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。较之NIO而言,AIO一方面简化了程序的编写,流的读取和写入都由操作系统来代替完成;另一方面省去了NIO中程序要遍历事件通知队列(Selector)的代价。Windows基于IOCP实现了AIO,Linux目前只有基于epoll模拟实现的AIO。
Java对TCP/IP和UDP/IP均支持,在网络IO的操作上,Java 7以前的版本仅支持BIO和NIO两种方式,对AIO方式感兴趣的读者可自行下载Sun JDK 7进行尝试。
2. 基于远程调用方式实现系统间的通信
当系统之间要通信时,可通过调用本地的一个Java接口的方法,透明地调用远程的Java实现。具体的细节则由Java或框架来完成,这种方式在Java中主要用来实现基于RMI和WebService的应用。
本章通过举例来介绍如何基于Java的包及开源的产品来实现以上两种方式的系统间通信,这些是实现分布式Java应用的基础和必备知识,采用的例子如下。
示例程序由一个服务器端程序和一个客户端程序构成,是典型的请求-响应机制,即客户端发送请求,服务端响应。客户端读取用户的输入,并将输入的字符串信息发送给服务器端,服务器端接收到信息后响应,当客户端输入的是quit字符串时,则停止客户端和服务器端的程序。