??xml version="1.0" encoding="utf-8" standalone="yes"?>
如果说Google的搜索引擎是免费的早,Gmail们是免费的午的话,
http://labs.google.com/papers/ 是Googlel开发h员们的一份免费的晚餐?/p>
不过Q咋看着一桌饭菜可能不知道从哪吃vQ在自己不熟悉的领域啃英文也不是一件愉快的事情?br />
q好Q有一位面试google不第的老兄Q自我爆发搞了一份Google Interal的PPTQ?/p>
http://cbcg.net/talks/googleinternals/index.htmlQ大安标点点就能跟着他匆匆过一遍google的内部架构?/p>
然后又有崮崮p\上走9?http://sharp838.mblogger.cn)与美Z?http://my.donews.com/eraera/)Q翻译了其中最重要的四份论文:
Google帝国Q便建立在大U?5万台的Server上,其中大部分都?cheap x86 boxes"。而这45万台ServerQ则建立于下面的key infrastructureQ?/p>
GFS是适用于大规模分布式数据处理应用的分布式文件系l,是Google一切的基础Q它Z普通的g讑֤Q实C定w的设计与极高的性能。 ?
李开复说QGoogle最厉害的技术是它的storage。我认ؓ学计机的学生都应该看看q篇文章Q再ơ感谢翻译的兄弟)。 ?br />
它以64MZ个Chunk(Block)Q每个Chunk臛_存在于三台机器上Q交互的单过E见:
MapReduce是一个分布式处理量数据集的~程模式Q让E序自动分布C个由普通机器组成的大集群上ƈ发执行。像Grep-style jobQ日志分析等都可以考虑采用它?br />
MapReduce的run-timepȝ会解册入数据的分布l节Q跨机器集的E序执行调度Q处理机器的失效Qƈ且管理机器之间的通讯h。这L模式允许E序员可以不需要有什么ƈ发处理或者分布式pȝ的经验,可以处理超大的分布式系l得资源?/p>
我自己接触MapReduce?a >Lucene->Nutch->Hadoop的\Uѝ?br /> Hadoop是Lucene之父Doug Cutting的又一力作Q是Java版本的分布式文gpȝ与Map/Reduce实现?br /> Hadoop的文档ƈ不详l,再看一?a >Googleq篇中文版的论文Q一切清晰很?又一ơ感谢翻译的兄弟)。 ?br />
孟岩也有一很清晰的博客:Map Reduce - the Free Lunch is not over?
BigTable 是Google Style的数据库Q用结构化的文件来存储数据?br /> 虽然不支持关pd数据查询Q但却是建立GFS/MapReduce基础上的Q分布式存储大规模结构化数据的方案?/p>
BigTable是一个稀疏的Q多l的Q排序的MapQ每个Cellp关键字,列关键字和时间戳三维定位QCell的内Ҏ一个不解释的字W串?br /> 比如下表存储每个|站的内容与被其他网站的反向q接的文本?br /> 反向的URL com.cnn.www(www.cnn.com)是行的关键字Qcontents列存储网内容,每个内容有一个时间戳Q因为有两个反向q接Q所以archor列族有两?anchor:cnnsi.com和anchhor:my.look.caQ列族的概念Q得表可以横向扩展Qarchor的列数ƈ不固定?/p>
Zq发dQ热区,HA{考虑QBigTable当然不会存在逗号分割的文本文件中Q,是存储在一U叫SSTable的数据库l构上,q有BMDiff和Zippy两种不同侧重点的压羃法?/p>
Sawzall是一U徏立在MapReduce基础上的领域语言Q可以被认ؓ是分布式的awk。它的程序控制结?if,while)与C语言无异Q但它的领域语言语义使它完成相同功能的代码与MapReduce的C++代码相比化了10倍不止?/p>
天书?慢慢看吧?/p>
我们q次是统计在每天24时里CVS提交的次数?br /> 首先它的变量定义cMPascal (i:int=0; 卛_义变量iQ类型ؓintQ初始gؓ0)
1:引入cvsstat.proto协议描述Q作用见后?br /> 2:定义int数组submits 存放l计l果Q用hour作下标?br /> 3.循环的将文g输入转换为ChangelistLog cdQ存储在log变量里,cd及{换方法在前面的cvsstat.proto描述?br /> 4.取出changlog中的提交旉log.time的hour倹{?br /> 5.emit聚合Q在sumitsl果数组里,hour的提交数?Q然后自动@环下一个输入?
居然L了,其中1?步是准备与定义,3?步是MapQ第5步是Reduce?br />
本文只是单的介绍Google的技术概貌,大家知道以后除了可作谈资外没有Q何作用,我们真正要学习的骨血Q是论文里如何解决高q发Q高可靠性等的设计思\和细?....
1.1 不用new关键词创建类的实?
用new关键词创建类的实例时Q构造函数链中的所有构造函数都会被自动调用。但如果一个对象实CCloneable接口Q我们可以调用它的clone()Ҏ。clone()Ҏ不会调用McL造函数?
在用设计模?Design Pattern)的场合,如果用Factory模式创徏对象Q则改用clone()Ҏ创徏新的对象实例非常单。例如,下面是Factory模式的一个典型实玎ͼ
public static Credit getNewCredit()
{
return new Credit();
}
改进后的代码使用clone()ҎQ如下所C:
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit()
{
return (Credit) BaseCredit.clone();
}
上面的思\对于数组处理同样很有用?
1.2 使用非阻塞I/O
版本较低的JDK不支持非dI/O API。ؓ避免I/OdQ一些应用采用了创徏大量U程的办?在较好的情况下,会用一个缓冲池)。这U技术可以在许多必须支持q发I/O的应用中见刎ͼ如Web服务器、报价和拍卖应用{。然而,创徏JavaU程需要相当可观的开销?
JDK 1.4引入了非d的I/O?java.nio)。如果应用要求用版本较早的JDKQ在q里有一个支持非dI/O的Y件包?
1.3 慎用异常
异常Ҏ能不利。抛出异帔R先要创徏一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本?(Native)ҎQfillInStackTrace()Ҏ查堆栈,攉调用跟踪信息。只要有异常被抛出,VM必调整调用堆栈,因ؓ在处理过E中创徏了一个新的对象?
异常只能用于错误处理Q不应该用来控制E序程?
1.4 不要重复初始化变?
默认情况下,调用cȝ构造函数时Q?Java会把变量初始化成定的|所有的对象被设|成nullQ整数变?byte、short、int、long)讄?Qfloat?double变量讄?.0Q逻辑D|成false。当一个类从另一个类zӞq一点尤其应该注意,因ؓ用new关键词创Z个对象时Q构造函数链中的所有构造函数都会被自动调用?
1.5 量指定cȝfinal修饰W?
带有final修饰W的cL不可z的。在Java核心API中,有许多应用final的例子,例如java.lang.String。ؓStringcL定final防止了h们覆盖length()Ҏ?
另外Q如果指定一个类为finalQ则该类所有的Ҏ都是final。Java~译器会LZ内联(inline)所有的finalҎ(q和具体的编译器实现有关)。此举能够性能q_提高50%?
1.6 量使用局部变?
调用Ҏ时传递的参数以及在调用中创徏的时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等Q都在堆(Heap)中创建,速度较慢。另外,依赖于具体的~译?JVMQ局部变量还可能得到q一步优化。请参见《尽可能使用堆栈变量》?
1.7 乘法和除?
考虑下面的代码:
for (val = 0; val < 100000; val +=5)
{
alterX = val * 8;
myResult = val * 2;
}
用移位操作替代乘法操作可以极大地提高性能。下面是修改后的代码Q?
for (val = 0; val < 100000; val += 5)
{
alterX = val << 3;
myResult = val << 1;
}
修改后的代码不再做乘?的操作,而是改用{h的左U?位操作,每左U?位相当于乘以2。相应地Q右U?位操作相当于除以2。值得一提的是,虽然UM操作速度快,但可能代码比较难于理解Q所以最好加上一些注释?/div>
Author
: zhyiwww
E-Mail
: zhyiwww@163.com
Date
: 2007-1-30
转蝲h明出?/span>
www.tkk7.com/zhyiwww
(copyright by @ zhangyi)
下面是我的一D代码,实现如何截取囄的:
private static final String SRC_FILE="org//zy//demo//jdk//base//image//car1.jpg";
//
目标囄
private static final String DEST_FILE="c://a.jpg";
/**
*
d囑փ文g
*
?/span>
ImageReader
* @param imgPath
* @throws IOException
*/
public void readUsingImageReader(String imgPath) throws IOException{
//
取得囄d?/span>
Iterator readers = ImageIO.getImageReadersByFormatName("jpg");
System.out.println(readers);
ImageReader reader = (ImageReader)readers.next();
System.out.println(reader);
//
取得囄d?/span>
InputStream source=this.parseImagePath(ImageDemo.SRC_FILE);
ImageInputStream iis = ImageIO.createImageInputStream(source);
reader.setInput(iis, true);
//
囄参数
ImageReadParam param = reader.getDefaultReadParam();
int imageIndex = 0;
int half_width = reader.getWidth(imageIndex)/2;
int half_height = reader.getHeight(imageIndex)/2;
// Rectangle rect = new Rectangle(60, 60, half_width, half_height);
Rectangle rect = new Rectangle(60, 60, 100, 100);
param.setSourceRegion(rect);
BufferedImage bi = reader.read(0,param);
ImageIO.write(bi, "jpg", this.initDestFile());
}
我的源图片是Q?/span>
<!--[endif]-->
上面的程序运行后截得的图片如下:
<!--[endif]-->
我们知道q程q程调用QRemote Procedure Call, RPCQ可以用于一个进E调用另一个进E(很可能在另一个远E主ZQ中?span>q程Q从而提供了q程的分布能力。Java ?RMI 则在 RPC 的基上向前又q进了一步,x供分布式 对象间的通讯Q允许我们获得在q程q程中的对象Q称E对象)的引用(UCؓq程引用Q,q而通过引用调用q程对象的方法,好像该对象是与你的客户端代码同栯行在本地q程中一栗RMI 使用了术?Ҏ"QMethodQ强调了q种q步Q即在分布式基础上,充分支持面向对象的特性?
RMI q不?Java 中支持远E方法调用的唯一选择。在 RMI 基础上发展而来?RMI-IIOPQJava Remote Method Invocation over the Internet Inter-ORB ProtocolQ,不但l承?RMI 的大部分优点Qƈ且可以兼容于 CORBA。J2EE ?EJB 都要求?RMI-IIOP 而不?RMI。尽如此,理解 RMI 大大有助于 RMI-IIOP 的理解。所以,即便你的兴趣?RMI-IIOP 或?EJBQ相信本文也会对你很有帮助。另外,如果你现在就?API 感兴,那么可以告诉你,RMI 使用 java.rmi 包,?RMI-IIOP 则既使用 java.rmi 也用扩展的 javax.rmi 包?
本文的随后内容将仅针?Java RMI?/p>
在学?RMI 之前Q我们需要了解一些基知识。首先需要了解所谓的分布式对象(Distributed ObjectQ。分布式对象是指一个对象可以被q程pȝ所调用。对?Java 而言Q即对象不仅可以被同一虚拟Z的其他客L序(ClientQ调用,也可以被q行于其他虚拟机中的客户E序调用Q甚臛_以通过|络被其他远E主Z上的客户E序调用?
下面的图C明了客户E序是如何调用分布式对象的:
从图上我们可以看刎ͼ分布式对象被调用的过E是q样的:
客户E序调用一个被UCؓ Stub Q有时译作存根,Z不生歧义,本文用其英文形式Q的客户端代理对象。该代理对象负责对客L隐藏|络通讯的细节。Stub 知道如何通过|络套接字(SocketQ发送调用,包括如何调用参数{换ؓ适当的Ş式以便传输等?
Stub 通过|络调用传递到服务器端Q也是分布对象一端的一个被UCؓ Skeleton 的代理对象。同P该代理对象负责对分布式对象隐藏网l通讯的细节。Skeleton 知道如何从网l套接字QSocketQ中接受调用Q包括如何将调用参数从网l传输Ş式{换ؓ Java 形式{?
Skeleton 调用传递给分布式对象。分布式对象执行相应的调用,之后返回g递给 SkeletonQ进而传递到 StubQ最l返回给客户E序?/p>
q个场景Z一个基本的法则Q即行ؓ的定义和行ؓ的具体实现相分离。如图所C,客户端代理对?Stub 和分布式对象都实C相同的接口,该接口称E接口(Remote InterfaceQ。正是该接口定义了行为,而分布式对象本n则提供具体的实现。对?Java RMI 而言Q我们用接口Q?font color="red">interfaceQ定义行为,用类Q?font color="red">classQ定义实现?
RMI 的底层架构由三层构成Q?
首先?Stub/Skeleton 层。该层提供了客户E序和服务程序彼此交互的接口?
然后是远E引用(Remote ReferenceQ层。这一层相当于在其之上?Stub/Skeleton 层和在其之下的传输协议层之前的中间gQ负责处理远E对象引用的创徏和管理?
最后是传输协议QTransport ProtocolQ?层。该层提供了数据协议Q用以通过U\传输客户E序和远E对象间的请求和应答?/p>
q些层之间的交互可以参照下面的示意图Q?
和其它分布式对象机制一PJava RMI 的客L序用客L?Stub 向远E对象请求方法调用;服务器对象则通过服务器端?Skeleton 接受h。我们深入进去,来看看其中的一些细节?
注意: 事实上,?Java 1.2 之后QRMI 不再需?Skeleton 对象Q而是通过 Java 的反机ӞReflectionQ来完成Ҏ务器端的q程对象的调用。ؓ了便于说明问题,本文以下内容仍然Z Skeleton 来讲解?/p>
当客L序调?Stub ӞStub 负责方法的参数转换为序列化QSerializedQŞ式,我们使用一个特D的术语Q即~列QMarshalQ来指代q个q程。编列的目的是将q些参数转换为可UL的Ş式,从而可以通过|络传输到远E的服务对象一端。不q的是,q个q程没有惌中那么简单。这里我们首先要理解一个经典的问题Q即Ҏ调用Ӟ参数I竟是传D是传引用呢?对于 Java RMI 来说Q存在四U情况,我们分别加以说明?
对于基本的原始类型(整型Q字W型{等Q,被自动的序列化Q以传值的方式~列?
对于 Java 的对象,如果该对象是可序列化的(实现?java.io.Serializable 接口Q,则通过 Java 序列化机制自动地加以序列化,以传值的方式~列。对象之中包含的原始cd以及所有被该对象引用,且没有声明ؓ transient 的对象也自动的序列化。当Ӟq些被引用的对象也必L可序列化的?
l大多数内徏?Java 对象都是可序列化的?对于不可序列化的 Java 对象Q?font color="red">java.io.File 最典型Q,或者对象中包含对不可序列化Q且没有声明?transient 的其它对象的引用。则~列q程向客户E序抛出异常Q而宣告失败?
客户E序可以调用q程对象Q没有理q止调用参数本w也是远E对象(实现?java.rmi.Remote 接口的类的实例)。此ӞRMI 采用一U?span>模拟?/i>传引用方式(当然不是传统意义的传引用Q因为本地对内存的引用到了远E变得毫无意义)Q而不是将参数直接~列复制到远E。这U情况下Q交互的双方发生的戏剧性变化值得我们注意。参数是q程对象Q意味着该参数对象可以远E调用。当客户E序指定q程对象作ؓ参数调用服务器端q程对象的方法时QRMI 的运行时机制向服务器端的远E对象发送作为参数的q程对象的一?Stub 对象。这h务器端的q程对象可以回调(CallbackQ这?Stub 对象的方法,q而调用在客户端的q程对象的对应方法。通过q种ҎQ服务器端的q程对象可以修改作为参数的客户端远E对象的内部状态,q正是传l意义的传引用所具备的特性。是不是有点晕?q里的关键是要明白,在分布式环境中,所谓服务器和客L都是相对的。被h的一方就是服务器Q而发求的一方就是客L?
在调用参数的~列q程成功后,客户端的q程引用层从 Stub 那里获得了编列后的参C及对服务器端q程对象的远E引用(参见 java.rmi.server.RemoteRef APIQ。该层负责将客户E序的请求依据底层的 RMI 数据传输协议转换Z输层h。在 RMI 中,有多U的可能的传输机Ӟ比如点对点(Point-to-PointQ以及广播(MulticastQ等。不q,在当前的 JMI 版本中只支持点对点协议,卌E引用层生成唯一的传输层hQ发往指定的唯一q程对象Q参?java.rmi.server.UnicastRemoteObject APIQ?
在服务器端,服务器端的远E引用层接收传输层请求,q将其{换ؓ对远E对象的服务器端代理对象 Skeleton 的调用。Skeleton 对象负责请求{换ؓ对实际的q程对象的方法调用。这是通过与编列过E相对的反编列(UnmarshalQ过E实现的。所有序列化的参数被转换?Java 形式Q其中作为参数的q程对象Q实际上发送的是远E引用)被{换ؓ服务器端本地?Stub 对象?
如果Ҏ调用有返回值或者抛出异常,?Skeleton 负责~列q回值或者异常,通过服务器端的远E引用层Q经传输层传递给客户端;相应圎ͼ客户端的q程引用层和 Stub 负责反编列ƈ最l将l果q回l客L序?
整个q程中,可能最让hqh的是q程引用层。这里只要明白,本地?Stub 对象是如何生的Q就不难理解q程引用的意义所在了。远E引用中包含了其所指向的远E对象的信息Q该q程引用用于构造作为本C理对象的 Stub 对象。构造后QStub 对象内部维护该q程引用。真正在|络上传输的实际上就是这个远E引用,而不?Stub 对象?/p>
?RMI 的基本架构之上,RMI 提供服务与分布式应用E序的一些对象服务,包括对象的命?注册QNaming/RegistryQ服务,q程对象Ȁz(ActivationQ服务以及分布式垃圾攉QDistributed Garbage Collection, DGCQ。作为入门指南,本文指介绍其中的命?注册服务Q因为它是实?RMI 所必备的。其它内容请读者自行参考其它更加深入的资料?
在前一节中Q如果你喜欢刨根问底Q可能已l注意到Q客L要调用远E对象,是通过其代理对?Stub 完成的,那么 Stub 最早是从哪里得来的呢?RMI 的命?注册服务正是解决q一问题的。当服务器端惛_客户端提供基?RMI 的服务时Q它需要将一个或多个q程对象注册到本地的 RMI 注册表中Q参?font color="red">java.rmi.registry.Registry APIQ。每个对象在注册旉被指定一个将来用于客L序引用该对象的名U。客L序通过命名服务Q参?java.rmi.Naming APIQ,指定cM URL 的对象名U就可以获得指向q程对象的远E引用。在 Naming 中的 lookup() Ҏ扑ֈq程对象所在的L后,它将索该L上的 RMI 注册表,q请求所需的远E对象。如果注册表发现被请求的q程对象Q它生成一个对该远E对象的q程引用Qƈ其q回l客LQ客L则基于远E引用生成相应的 Stub 对象Qƈ引用传递给调用者。之后,双方可以按照我们前面讲q的方式q行交互了?
注意: RMI 命名服务提供?Naming cdƈ不是你的唯一选择。RMI 的注册表可以与其他命名服务绑定,比如 JNDIQ这样你可以通过 JNDI 来访?RMI 的注册表了?/p>
理论M开实践Q理?RMI 的最好办法就是通过例子。开?RMI 的分布式对象的大体过E包括如下几步:
定义q程接口。这一步是通过扩展 java.rmi.Remote 接口Qƈ定义所需的业务方法实现的?
定义q程接口的实现类。即实现上一步所定义的接口,l出业务Ҏ的具体实现逻辑?
~译q程接口和实现类Qƈ通过 RMI ~译?rmic Z实现cȝ成所需?Stub ?Skeleton cR?/p>
RMI 中各个组件之间的关系如下面这个示意图所C:
回忆我们上一节所讲的QStub ?Skeleton 负责代理客户和服务器之间的通讯。但我们q不需要自q成它们,相反QRMI 的编译器 rmic 可以帮我们基于远E接口和实现cȝ成这些类。当客户端对象通过命名服务向服务器端的 RMI 注册表请求远E对象时QRMI 自动构造对应远E对象的 Skeleton 实例对象Qƈ通过 Skeleton 对象远E引用返回给客户端。在客户端,该远E引用将用于构?Stub cȝ实例对象。之后,Stub 对象?Skeleton 对象可以代理客户对象和q程对象之间的交互了?
我们的例子展C一个简单的应用场景。服务器端部|了一个计引擎,负责接受来自客户端的计算dQ在服务器端执行计算dQƈ结果返回给客户端。客L发送ƈ调用计算引擎的计Q务实际上是计指定精度的 π 倹{?
重要: 本文的例子改~自 The Java?Tutorial Trail:RMI。所有权利属于相应的所有h?/p>
定义q程接口与非分布式应用中定义接口的方法没有太多的区别。只要遵守下面两个要求:
q程接口必须直接或者间接的扩展?java.rmi.Remote 接口。远E接口还可以在扩展该接口的基上,同时扩展其它接口Q只要被扩展的接口的所有方法与q程接口的所有方法一h下一个要求?
在远E接口或者其接口(Super-interfaceQ中声明的方法必L下列对q程Ҏ的要求:
q程Ҏ必须声明抛出 java.rmi.RemoteException 异常Q或者该异常的超c(SuperclassQ,比如 java.io.IOException 或?java.lang.Exception 异常。在此基上,q程Ҏ可以声明抛出应用特定的其它异常?
在远E方法声明中Q作为参数或者返回值的q程对象Q或者包含在其它非远E对象中的远E对象,必须声明为其对应的远E接口,而不是实际的实现cR?/p>
注意: ?Java 1.2 之前Q上面关于抛出异常的要求更严|卛_L?java.rmi.RemoteExcptionQ不允许cM java.io.IOException q样的超cR现在之所以放宽了q一要求Q是希望可以使定义既可以用于q程对象Q也可以用于本地对象的接口变得容易一些(x EJB 中的本地接口和远E接口)。当Ӟqƈ没有佉K题好多少Q你q是必须声明异常。不q,一U观点认不是问题Q强制声明异常可以开发h员保持清醒的头脑Q因E对象和本地对象在调用时传参的语意是不同的。本地对象是传引用,而远E对象主要是传|q意呛_参数内部状态的修改产生的结果是不同的?
对于W一个要求,java.rmi.Remote 接口实际上没有Q何方法,而只是用作标记接口。RMI 的运行环境依赖该接口判断对象是否是远E对象。第二个要求则是因ؓ分布式应用可能发生Q何问题,比如|络问题{等?
?1 列出了我们的q程接口定义。该接口只有一个方法:executeTask() 用以执行指定的计Q务,q返回相应的l果。注意,我们用后~ Remote 表明接口是远E接口?
package rmitutorial; import java.rmi.Remote; import java.rmi.RemoteException; public interface ComputeEngineRemote extends Remote { public Object executeTask(Task task) throws RemoteException; }
?2 列出了计Q务接口的定义。该接口也只有一个方法:execute() 用以执行实际的计逻辑Qƈq回l果。注意,该接口不是远E接口,所以没有扩?java.rmi.Remote 接口Q其Ҏ也不必抛?java.rmi.RemoteException 异常。但是,因ؓ它将用作q程Ҏ的参敎ͼ所以扩展了 java.io.Serializable 接口?
接下来,我们实现前面定义的q程接口?a>?3l出了实现的源代码?
package rmitutorial; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class ComputeEngine extends UnicastRemoteObject implements ComputeEngineRemote { public ComputeEngine() throws RemoteException { super(); } public Object executeTask(Task task) throws RemoteException { return task.execute(); } }
c?ComputeEngine 实现了之前定义的q程接口Q同时承自 java.rmi.server.UnicastRemoteObject 类?font color="red">UnicastRemoteObject cL一个便LQ它实现了我们前面所讲的Z TCP/IP 的点对点通讯机制。远E对象都必须从该cL展(除非你想自己实现几乎所?UnicastRemoteObject 的方法)。在我们的实现类的构造函CQ调用了类的构造函敎ͼ当然Q即使你不显式的调用q个构徏函数Q它也一样会被调用。这里这样做Q只是ؓ了突出强调这U调用而已Q。该构造函数的最重要的意义就是调?UnicastRemoteObject cȝ exportObject() Ҏ。导出(ExportQ对象是指ɘq程对象准备qAQ可以接受进来的调用的过E。而这个过E的最重要内容是建立服务器套接字Q监听特定的端口Q等待客L的调用请求?/p>
Z让客L序可以找到我们的q程对象Q就需要将我们的远E对象注册到 RMI 的注册表。这个过E有时被UCؓ"引导"q程QBootstrapQ。我们将为此~写一个独立的引导E序负责创徏和注册远E对象?a>?4 l出了引导程序的源代码?
package rmitutorial; import java.rmi.Naming; import java.rmi.RMISecurityManager; public class Bootstrap { public static void main(String[] args) throws Exception { String name = "ComputeEngine"; ComputeEngine engine = new ComputeEngine(); System.out.println("ComputerEngine exported"); Naming.rebind(name, engine); System.out.println("ComputeEngine bound"); } }
可以看到Q我们首先创Z一个远E对象(同时导出了该对象Q,之后该对象l定?RMI 注册表中?font color="red">Naming ?rebind() Ҏ接受一?URL 形式的名字作l定之用。其完整格式如下Q?a>
其中Q协议(ProtocolQ默认ؓ rmiQ主机名默认?localhostQ端口默认ؓ 1099。注意,JDK 中提供的默认 Naming 实现只支?rmi 协议。在我们的引导程序里面只l出了对象绑定的名字Q而其它部分均使用~省倹{?/p>
?5 l出了我们的客户端程序。该E序接受两个参数Q分别是q程对象所在的L地址和希望获得的 π 值的_ֺ?
package rmitutorial; import java.math.BigDecimal; import java.rmi.Naming; public class Client { public static void main(String args[]) throws Exception { String name = "rmi://" + args[0] + "/ComputeEngine"; ComputeEngineRemote engineRemote = (ComputeEngineRemote)Naming.lookup(name); Pi task = new Pi(Integer.parseInt(args[1])); BigDecimal pi = (BigDecimal)(engineRemote.executeTask(task)); System.out.println(pi); } }
package rmitutorial; import java.math.*; public class Pi implements Task { private static final BigDecimal ZERO = BigDecimal.valueOf(0); private static final BigDecimal ONE = BigDecimal.valueOf(1); private static final BigDecimal FOUR = BigDecimal.valueOf(4); private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN; private int digits; public Pi(int digits) { this.digits = digits; } public Object execute() { return computePi(digits); } public static BigDecimal computePi(int digits) { int scale = digits + 5; BigDecimal arctan1_5 = arctan(5, scale); BigDecimal arctan1_239 = arctan(239, scale); BigDecimal pi = arctan1_5.multiply(FOUR).subtract( arctan1_239).multiply(FOUR); return pi.setScale(digits, BigDecimal.ROUND_HALF_UP); } public static BigDecimal arctan(int inverseX, int scale) { BigDecimal result, numer, term; BigDecimal invX = BigDecimal.valueOf(inverseX); BigDecimal invX2 = BigDecimal.valueOf(inverseX * inverseX); numer = ONE.divide(invX, scale, roundingMode); result = numer; int i = 1; do { numer = numer.divide(invX2, scale, roundingMode); int denom = 2 * i + 1; term = numer.divide(BigDecimal.valueOf(denom), scale, roundingMode); if ((i % 2) != 0) { result = result.subtract(term); } else { result = result.add(term); } i++; } while (term.compareTo(ZERO) != 0); return result; } }
~译我们的示例程序和~译其它非分布式的应用没什么区别。只是编译之后,需要?RMI ~译器,?rmic 生成所需 Stub ?Skeleton 实现。?rmic 的方式是我们的q程对象的实现类Q不是远E接口)的全cd作ؓ参数来运?rmic 命o。参考下面的CZQ?a>
E:\classes\rmic rmitutorial.ComputeEngine
~译之后生?rmitutorial.ComputeEngine_Skel ?rmitutorial.ComputeEngine_Stub 两个cR?/p>
q程对象的引用通常是通过 RMI 的注册表服务以及 java.rmi.Naming 接口获得的。远E对象需要导出(注册Q相应的q程引用到注册表服务Q之后注册表服务可以监听ƈ服务于客L对远E对象引用的h。标准的 Sun Java SDK 提供了一个简单的 RMI 注册表服务程序,?rmiregistry 用于监听特定的端口,{待q程对象的注册,以及客户端对q些q程对象引用的检索请求?
在运行我们的CZE序之前Q首先要启动 RMI 的注册表服务。这个过E很单,只要直接q行 rmiregistry 命o卛_。缺省的情况下,该服务将监听 1099 端口。如果需要指定其它的监听端口Q可以在命o行指定希望监听的端口Q如果你指定了其它端口,需要修改示例程序以适应环境Q。如果希望该E序在后台运行,?Unix 上可以以如下方式q行Q当Ӟ可以~省端口参数Q:
$ rmiregistry 1099 &
C:\> start rmiregistry 1099
我们?rmitutorial.Bootstrap cd用于启动q程对象Qƈ其l定?RMI 注册表中。运行该cdQ远E对象也进入监听状态,{待来自客户端的Ҏ调用h?a>
$ java rmitutorial.Bootstrap ComputeEngine exported ComputeEngine bound
启动q程对象后,打开另一个命令行H口Q运行客L。命令行的第一个参Cؓ RMI 注册表的地址Q第二个参数为期望的 π 值精度。参考下面的CZQ?a>
$ java rmitutorial.Client localhost 50 3.14159265358979323846264338327950288419716939937511
在演C示例程序时Q我们实际上是在同一L上运行的服务器和客户端,q且无论是服务器和客L所需的类都在相同的类路径上,可以同时被服务器和客L所讉K。这忽略?Java RMI 的一个重要细节,卛_态类装蝲。因?RMI 的特性(包括其它几个Ҏ)q不适用?J2EE ?RMI-IIOP ?EJB 技术,所以,本文不作详l介l,误者自行参考本文给出的参考资料。不q,Z让好奇的读者不至于q分失望Q这里简单介l一下动态类装蝲的基本思想?
RMI q行时系l采用动态类装蝲机制来装载分布式应用所需的类。如果你可以直接讉K应用所涉及的所有包括服务器端客L在内的主机,q且可以把分布式应用所需的所有类都安装在每个L?CLASSPATH
中(上面的示例就是极端情况,所有的东西都在本地LQ,那么你完全不必关?RMI c装载的l节。显Ӟ既然是分布式应用Q情况往往正相反。对?RMI 应用Q客L需要装载客L自n所需的类Q将要调用的q程对象的远E接口类以及对应?Stub c;服务器端则要装蝲q程对象的实现类以及对应?Skeleton c(Java 1.2 之后不需?Skeleton c)。RMI 在处理远E调用涉及的q程引用Q参C及返回值时Q可以将一个指定的 URL ~码到流中。交互的另一端可以通过 ?URL 获得处理q些对象所需的类文g。这一点类g Applet 中的 CODEBASE 的概念,交互的两端通过 HTTP 服务器发布各自控制的c,允许交互的另一端动态下载这些类。以我们的示例ؓ例,客户端不必部|?ComputeEngine_Stub 的类文gQ而可以通过服务器端?HTTP 服务器获得类文g。同P服务器端也不需要客L实现的定制Q?Pi 的类文g?
注意Q这U动态类装蝲需要交互的两端加蝲定制的安全管理器Q参?java.rmi.RMISecurityManager APIQ,以及对应的策略文件?/p>
David Flanagan, Jim Farley, William Crawford and Kris Magnusson, 1999, ISBN 1-56592-483-5E, O'Reilly, Java?Enterprise in a Nutshell
Ed Roman, Scott Ambler and Tyler Jewell 2002, ISBN 0-471-41711-4, John Wiley &Sons, Inc., Matering Enterprise JavaBeans?/i> , Second Edition