分布式Ruby--杀手级Ruby应用(源于我的Ruby新书)

11  分布式Ruby

分布式技术主要是为了满足单台计算机的计算能力不足、而又无法使用超级计算机(多CPUTB级别内存)的情况,它通过多台计算机的累加,把计算量和数据分布到每一台机器上,以达到超级计算机的效果。

比如在计算PageRank时,100亿个网页链接的ID号组成的稀疏矩阵,将会消耗超过TB的内存,普通计算机根本无法完成它的计算量。这时,通过几千甚至上万台机器一起运算,把运算量分散到多台机器上,可以完成超级计算机才能够完成的计算任务。本章主要介绍Ruby的分布式技术和它的相关技术。

更多请访问http://www.jobbos.cn/

11.1  概述

分布式应用是在由通信网络上互联的多台服务器上,共同完成一个任务的应用,分布式Ruby(或者说DRbdruby)是一个完全由Ruby编写的分布式应用开发框架,它诞生于1999年,支持drubydrbunix内部协议。

通过把运算任务分散到多台计算机,可以实现多台计算机一起计算,使计算速度得以线性提高。而分布式Ruby技术,正是可以搭建这种分布式计算的平台。

分布式RubyRPCCORBAJava RMISOAP以及DCOM等远端调用和分布式技术类似,通过DRb技术,开发者可以非常迅速地给客户提供一个分布式计算的解决方案。DRb技术以对象的深度序列化技术(Marshal)为基础,不但支持简单对象的网络传递,更可以做到面向目的编程。DRb拥有以下优势:

l         DRb是简洁的。搭建DRb分布式解决方案,一般只是搭建一个CORBASOAP平台应用的几分之一甚至十几分之一。

l         DRb支持动态调整。通过修改Ruby的部分源文件,可以在运行时改变部分服务的实现细节。

l         DRb隐藏了调度和简单对象持久化的技术细节。通过DRb编写应用解决方案可以将对象传递的顾虑降至最小。

l         DRb不畏惧未知的潜在需求。通过eval功能,使任何应用的开发和部署都变得简洁快速。

l         DRb采用了Ruby语言,能充分发挥Ruby的强大特性。

Ruby强大的文本处理能力和DRb强大敏捷的分布式开发能力,使Ruby可以被快速地应用在各个领域。可以说,分布式RubyRuby语言最强大的杀手级应用(Kill Application)之一。

本章通过介绍循序渐进的方式介绍分布式Ruby和它相关的极其重要的技术――Marshal(持久化)。

11.2  简单的DRb例程

下面的例子演示了一个简单的分布式Ruby的应用。在这个简单的例程中,可以通过打开多个DRbClient程序连接到DRb服务器上,并且能够调用服务器提供者ServObjexecute方法并取得它的返回值。

1DRb服务器

下面的代码通过DRb模块的start_service方法,启动了一个分布式系统服务器处理系统。该系统通过ServObj提供服务。

代码11.1 简单的DRb例程:

#!/usr/bin/ruby

#DRb例程

require 'drb/drb'

 

class HelloDRbServer

    def execute

        "Hello, Distributed Ruby"

    end

end

 

#创建为DRb服务器提供服务的对象

ServObj = HelloDRbServer.new

#使用创建的ServObj对象作为DRb服务提供者

DRb.start_service('druby://localhost:8888', ServObj)

DRb.thread.join

2DRb客户端

下面的代码通过DRbObject对象连接到DRb服务器,生成DRbObject对象实例。DRbObject的对象实例可以执行任意ServObj的方法。

代码11.2  DRb客户端:

require 'drb'

#连接DRb服务器,采用的是druby协议

obj = DRbObject.new(nil, 'druby://localhost:8888')

p obj.execute

客户端连接到DRb服务器,得到分布式Ruby服务实体对象DRbObject实例obj,通过obj调用类HelloDRbServerexecute方法,得到执行结果。

"Hello, Distributed Ruby"

在上面的例子中DRb服务器将HelloDRbServer对象的实例注册为分布式Ruby服务器的服务实体。DRbObject的第一参数nil表示要注册一个新的DRbObject,也可以注册以前生成的DRbObject

分布式Ruby通过启动本地的分布式服务器,隐藏了服务对象实体和外部的交互,方法的参数和返回值都是用marshalledunmarshalled方法传递的,但是这些细节都由druby协议隐藏了起来,对程序员完全透明。DRb库提供了以下几个部分:

l         drb  DRb的主程序。

l         acl  Access control list

l         Rinda tuple-space  分布式工作管理系统,LindaRuby版本。

11.3  对象的持久化Marshal

持久化又称序列化或对象的序列化(Serialization)。所谓对象的持久化,是保存和恢复对象状态的一种方式。一般,程序在运行时产生的对象,会随着程序的关闭而被释放,但如果想把运行期对象的特性保存下来,在程序终止运行后,尽管这些对象实例被释放了,但是仍然可以通过在程序中重新加载这些特性,从而将这些对象实例还原成原来的状态,以进行场景重现。

11.3.1   什么是持久化

实际上,持久化技术的历史远远比Java早。从最古老的游戏保存记录,到目前网络游戏中的保存人物状态,都是持久化技术的一种体现。

持久化技术在众多领域都有广泛的应用,其中最常见的就是图形图像软件。图形软件的一个很重要功能就是场景重现,PhotoshopAutoCAD等软件将用户的操作保存到硬盘中,在用户下一次加载这些工程文件时,工程会还原每一个图形元件所在的位置以及用户的操作顺序。一个支持序列化的对象必须支持以下特征:

l         持久化接口必须是虚接口。

l         没有成员变量的对象都支持持久化。

l         持久化对象必须支持数据的序列化导入和导出。

l         如果一个对象支持持久化,那么这个对象的每一个祖先必须支持持久化。

l         如果持久化类的子类不支持持久化,那么该子类的所有子孙都不支持持久化。

11.3.2   Marshal技术

Ruby通过Marshal层和对象层的序列化方法接口实现数据持久化。

对象层通过实例方法_dump(转储)和类方法_load(加载)实现对象的还原和场景重现。

l         _dump  obj._dump(depth)返回一个字符串,这个字符串包含了能够还原这个对象的所有信息,这里的depth是序列化深度。

l         _load  ClassName._load(string)读取_dump输出的字符串,返回一个重新生成的对象,前提是解释器能够找到这个对象的定义,ClassName是它的类名。

Marshal层提供了两个类方法:类方法dump(转储)和类方法load(加载),用于序列化一个对象和所有它的派生子类。

l         dump  Marshal.dump(obj, obj, depth=-1 )返回一个io对象或数据,这些数据包括obj的所有亲属的信息,可以将对象还原。

l         load  Marshal.load( data) -> obj通过dump出的数据还原对象。

可以认为是Marshal层的持久化方法支配对象层的持久化接口实现了Ruby对象的持久化操作,Marsha1技术的简单实例如下所示。

代码11.3  Marshal技术:

#!/usr/bin/ruby

#Marshal技术演示

class Foo

    def initialize(str)

        @hello = str

    end

    def Hello

        @hello

    end

end

 

#下面代码对对象实例进行序列化

obj = Foo.new("Hello World/n") 

data = Marshal.dump(obj) 

obj = Marshal.load(data) 

obj.Hello                      >>Hello World

这段代码通过Marshaldump方法导出对象实例obj的持久化数据,并且通过Marshalload方法,根据持久化的数据,重新还原出了一个Foo的对象实例。它和obj实例在任何性质上都是相同的。

Marshal还提供了一个名为restore的类方法,它和类方法load的使用方法和功能都一样。不过Marshal序列化技术是有局限性的,如果一个对象需要被还原,那么解释器必须能够找到这个对象的定义。在下面的例子中便演示了如果没有找到一个对象的定义,Marshal技术的执行结果。

例子中有两个文件,其中,文件marshalsave.rb用于保存对象序列化的数据,文件marshalload.rb用于将对象还原。

代码11.4  Marshal技术-保存对象序列化数据marshalsave.rb

#!/usr/bin/ruby

#marshalsave.rb

#Marshal技术例程,保存对象

class Foo

    def initialize(str)

        @hello = str

    end

    def Hello

        @hello

    end

end

 

#下面对对象实例进行序列化

obj = Foo.new("Hello World/n") 

data = Marshal.dump(obj)

File.new('testfile','w+') do |f|

    f.write(data)

end

代码11.5 Marshal技术-场景还原marshalload.rb

#!/usr/bin/ruby

#marshalload.rb

#Marshal技术例程,还原对象

$data = ''

 

File.new('testfile') do |f|

    $data = f.read

end

 

obj = Marshal.load($data)        >> undefined method `Foo' for class `Object' (NameError)

p obj.Hello

代码11.5的实例中,用于还原数据的Marshal操作在解释器无法发现类名“Foo”之后,报告了undefined method `Foo' for class `Object'NameError)错误。说明解释器无法从“类常量表”中找到“Foo”这个类的定义,因此无法对$data进行场景重现。

同时,解释器找到的这个对象的类的定义必须和原来的定义相同,否则还原出的对象将不是原来保存的对象。下面的示例通过重新定义“Foo”类,将一个来自Lucy的问候,变成了来自Lily的问候。

代码11.6  Marshal技术―伪重现:

#!/usr/bin/ruby

#错误的还原对象的Marshal技术例程

class Foo

  def initialize(str)

    @hello = str

  end

  def Hello

    'I am '+@hello

  end

end

 

 

obj = Foo.new("Lucy") 

data = Marshal.dump(obj) 

 

obj = Marshal.load(data) 

p obj.Hello                    >>I am Lucy

 

#这里,我们重新定义了Foo

class Foo

      undef Hello

      def Hello

            @hello = 'Lily'

            'I am '+@hello

      end

end

 

#下面发现,这个名叫Lucy的女孩子变成了Lily

obj2 = Marshal.load(data) 

p obj2.Hello                    >>I am Lily

11.3.3   实现对象的序列化

本节的例子展示了如何通过类方法_dump_load实现对象的序列化。在下面的例子中可以看到,类方法_dump_load被定义后并没有显式调用,它们只负责将对象自己的属性加载或导出。而外围操作都由Marshal模块的dumpload方法负责实现。

代码11.7  _dump_load方法示例:

#!/usr/bin/ruby

#定义对象的序列化方法

class Foo

  def initialize(valueouter)

    @valueouter = valueouter

    @valueinner = " Great "

  end

 

  def _dump(depth)

    @valueouter.to_str

  end

 

  def Foo._load(str)

    result = Foo.new(str);

  end

 

  def to_s

    @valueouter + ", "+@valueinner + " ! "

  end

end

 

#dumpload只由Marshal类操作

a = Foo.new("Hello, World")

data = Marshal.dump(a)

obj = Marshal.load(data)

puts obj

这段程序的输出结果如下所示。

Hello, World,  Great  !

11.4  DRb对象

11.4.1   DRb对象

DRuby对象分为DRb::DRbObjectDRb::DRbServer。其中:

DRb::DRbServer在服务器端用于建立服务进程,处理客户端的请求。它把客户端出来的网络包转化称为对提供服务的ServerObject的方法调用,再把返回值传回给客户端。下面的代码就是用来建立一个利用ServObj作为前端对象(front object)提供服务的分布式Ruby服务器,'druby://localhost:8888'是这个服务器在网络上的URI标识。

DRb.start_service('druby://localhost:8888', ServObj)

DRb::DRbObject用于从远端服务器获取对象的引用。DRbObject通过服务器的URI连接到druby服务器,得到面向DRbServer的前端对象,进而得到针对ServObj的引用,DRbObject对象的方法如下:

obj = DRbObject.new(nil, 'druby://localhost:8888')

11.4.2   参数的传递

客户端调用ServObj的方法大都和在本地端调用一样,只需要注意ServObj的方法名称尽量不要和DRb::DRbObject的方法重名(重名时它会先调用本地的方法)。任何的基本类型对象都可以被当做参数或者返回值,它们在客户端被序列化,然后在服务器端被还原。

基于这样的原则,11.2节的例子可以被更改成如下代码的形式,其中,方法execute可以传递方法参数。

代码11.8  简单参数传递-服务器端:

#!/usr/bin/ruby

#参数传递例程的服务器端

require 'drb/drb'

 

class HelloDRbServer

    def execute(t)

        "Hello, Distributed Ruby at :" + t.to_s

    end

end

 

ServObj = HelloDRbServer.new

DRb.start_service('druby://localhost:8888', ServObj)

DRb.thread.join

代码11.9  简单参数传递-客户端:

#!/usr/bin/ruby

#参数传递例程的客户端

require 'drb'

obj = DRbObject.new(nil, 'druby://localhost:8888')

p obj.execute(Time.now)

这段程序的输出结果如下所示,客户端程序可以打印出服务器端程序执行之后的返回值。

"Hello, Distributed Ruby at :Sun Jun 09 22:36:14 China Standard Time 2006"

对于那些不能被自动序列化的复杂对象,被传递的参数是它的引用。例如,一个新建的复杂类,客户端的DRb::DRbObject不知道如何对它进行序列化,那么在调用远端方法处理这个对象时,就会产生错误或异常。

代码11.10  复杂参数传递-服务器端:

#!/usr/bin/ruby

#未定义的复杂对象传递演示

#服务器端

require 'drb'

 

$SAFE = 1

 

class DRbEx

      def initialize

            @hello = 'hello'

      end

 

      def hello

            @hello + ' '+Time.now.to_s

      end

 

      def returnobj(obj)

            obj.time

      end

 

end

 

if __FILE__ == $0

  DRb.start_service('druby://localhost:8886', DRbEx.new)

  puts DRb.uri

  DRb.thread.join

end

代码11.11  复杂参数传递-客户端:

#!/usr/bin/ruby

#未定义的复杂对象传递

#客户端

require 'drb'

class NewObject

      attr :starttime ,:time

      def initailize

            @starttime=Time.now

      end

      def get_localtime

            @time = Time.now

            @time

      end

end

 

#新建一个客户端

ro = DRbObject::new_with_uri("druby://localhost:8886")

p ro.hello

p ro.returnobj(NewObject.new)

这段程序的输出结果如下所示。

"hello Sun Jun 09 21:43:40 China Standard Time 2006"

(druby://localhost:8886) drbs-acl.rb:15:in `returnobj': undefined method `time' for #<DRb::DRbUnknown:0x2d0dba8> (NoMethodError)

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1552:in `perform_without_block'

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1512:in `perform'

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1586:in `main_loop'

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1582:in `main_loop'

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1578:in `main_loop'

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1427:in `run'

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1424:in `run'

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1344:in `initialize'

   from (druby://localhost:8886) c:/ruby/lib/ruby/1.8/drb/drb.rb:1624:in `start_service'

   from (druby://localhost:8886) drbs-acl.rb:21

   from testcl.rb:14

在服务器端不知道客户端传递来的引用是哪一种类型时,上面这种情况的出现是无法解决的,因此应当尽量避免传递客户端的对象引用给服务器端。

如果必须需要传递这种未定义的对象给服务器,并且让服务器能够识别这种对象,可以让服务器在服务器端打开eval权限,并且将这种新对象的代码执行一遍,使服务器可以识别这种对象。

11.4.3   复杂对象的传递

DRb服务器对于传递过来的参数使用Marshal技术进行序列化场景还原时,必须能够在自己的类常量表中找到传递过来的对象的类定义。因此必须在服务器和客户端都对该对象进行定义。

例如,在下面的例子中,在服务器端拷贝了NewObject的定义,使服务器端的解释器可以找到NewObject的类常量定义,从而可以对NewObject的实例进行还原。

代码11.12  复杂对象传递-服务器端:

#!/usr/bin/ruby

#复杂对象传递,服务器端

require 'drb'

 

class NewObject

  def initialize(valueouter)

    @valueouter = valueouter

    @valueinner = "Greet"

  end

 

  def _dump(depth)

    @valueouter.to_str

  end

 

  def self._load(str)

    result = new(str);

  end

 

  def to_s

        @valueouter + ' and ' +@valueinner

  end

end

 

 

#$SAFE=1

def outeval(codes)

      eval(codes)

end

class DRbEx

      def initialize

            @hello = 'hello'

      end

 

      def hello

            @hello + ' '+Time.now.to_s

      end

 

      def returnobj(obj)

            Marshal.load(obj).to_s

      end

      def evalcode(codes)

            outeval(codes)

      end

end

 

 

if __FILE__ == $0

  DRb.start_service('druby://localhost:8886', DRbEx.new)

  puts DRb.uri

  DRb.thread.join

end

代码11.13 复杂对象传递-客户端:

#!/usr/bin/ruby

#复杂对象传递,客户端

require 'drb'

class NewObject

  def initialize(valueouter)

    @valueouter = valueouter

    @valueinner = "Greet"

  end

 

  def _dump(depth)

    @valueouter.to_str

  end

 

  def self._load(str)

    result = new(str);

  end

 

  def to_s

        @valueouter + ' and ' +@valueinner

  end

end

 

ro = DRbObject::new_with_uri("druby://localhost:8886")

p ro.hello

ro.evalcode(codes)

p ro.returnobj(Marshal.dump(NewObject.new('localhost')))

11.4.4   未知对象的传递

在一般语言中,复杂对象的传递可以通过定义标准的序列化方式、以及建立分布式节点之间相关约定的机制来实现。但是未知的复杂对象的传递,对于其他所有语言都是一个很难解决的问题,而Ruby的解释型动态语言特性,使这个问题变得极为简单。

未知对象的传递必须遵循几个原则:

l         对象必须支持持久化。

l         必须能够找到机制让服务器能够识别该对象。

l         尽量不要定义和服务器现存对象同名对象。

在纯解释型动态语言中,让服务器能够识别该对象比较简单,Ruby中的eval方法和load加载机制都可以实现。

l         Eval机制  通过HERE文档或文件IO将对象定义文件以字符串的形式传过去,通过调用Kernel::eval执行这段字符串在服务器端生成对象的定义。

l         Load机制  将传送过去的对象定义文件存放到特定目录,使用Kernel::load方法加载该文件以生成对象的定义。

相比而言,load机制更加安全,而eval机制则更加灵活。在使用eval机制时候有一个限制:执行eval的代码必须在服务器类的定义以外,而且是以全局函数的形式出现,否则Marshal模块将无法对该对象进行序列化。因为,在服务器类中执行eval,会导致对象的名字空间会限定于服务器类之内。如下面的代码中,NewObject类将被定义成为DRbEx::NewObject

下面的示例展示了如何通过eval方法在服务器和客户端之间传递未知对象。它采用HERE文档的方式传递对象定义,在服务器端通过全局方法outeval执行,并生成该对象的定义。

代码11.14 未知对象传递-服务器端:

#!/usr/bin/ruby

require 'drb'

 

#$SAFE=1

#在服务器端实现未知对象功能的方法

def outeval(codes)

      eval(codes)

end

class DRbEx

      def initialize

            @hello = 'hello'

      end

 

      def hello

            @hello + ' '+Time.now.to_s

      end

 

      def returnobj(obj)

            Marshal.load(obj).to_s

      end

      def evalcode(codes)

            outeval(codes)

      end

end

 

 

if __FILE__ == $0

  DRb.start_service('druby://localhost:8886', DRbEx.new)

  puts DRb.uri

  DRb.thread.join

end

代码11.15 未知对象传递-客户端:

#!/usr/bin/ruby

require 'drb'

class NewObject

  def initialize(valueouter)

    @valueouter = valueouter

    @valueinner = "Greet"

  end

 

  def _dump(depth)

    @valueouter.to_str

  end

 

  def self._load(str)

    result = new(str);

  end

 

  def to_s

        @valueouter + ' and ' +@valueinner

  end

end

 

#通过here文档将类的定义代码保存

codes = <<HERE

class NewObject

  def initialize(valueouter)

    @valueouter = valueouter

    @valueinner = "Greet"

  end

 

  def _dump(depth)

    @valueouter.to_str

  end

 

  def self._load(str)

    result = new(str);

  end

 

  def to_s

    @valueouter + ' and ' +@valueinner

  end

end

HERE

 

ro = DRbObject::new_with_uri("druby://localhost:8886")

p ro.hello

 

#NewObject的定义传送到服务器端

#调用服务器端的evalcode方法

ro.evalcode(codes)

p ro.returnobj(Marshal.dump(NewObject.new('localhost')))         >>localhost and Greet

11.5  DRb安全

分布式Ruby通过设定$SAFE安全级别和ACL(访问控制列表)来实现远程访问的安全。

l         $SAFE  Ruby的默认保留全局变量,用于控制被污染线程或类的访问权限。

l         ACL(访问控制列表)  DRb的开发者专门为DRb设计的访问控制组件,它位于drb的安装目录。

11.5.1   屏蔽eval操作

默认情况下,面向外部的DRb服务允许使用服务对象的所有方法,这就意味着eval之类的方法被执行是可能的,这就产生了相当大的隐患。例如,以下代码是不安全的。为了更有效地理解DRb安全,下面先演示客户端的代码。

代码11.16  eval危害演示-客户端:

#!/usr/bin/ruby

require 'drb/drb'

ro = DRbObject.new(nil, "druby://localhost:8887")

 

class << ro

  undef :instance_eval  # force call to be passed to remote object

end

ro.instance_eval("`notepad.exe`")

代码11.17  eval危害演示-服务器端:

#!/usr/bin/ruby

require 'drb/drb'

 

#如果需要屏蔽eval,则设置$SAFE=1

#$SAFE=1

# The URI for the server to connect to

URI="druby://localhost:8887"

 

class TimeServer

 

  def get_current_time

    return Time.now

  end

 

end

 

# The object that handles requests on the server

FRONT_OBJECT=TimeServer.new

 

DRb.start_service(URI, FRONT_OBJECT)

# Wait for the drb server thread to finish before exiting.

DRb.thread.join

这段代码可以让服务器启动NotePad.exe,可以使DRb的远程对象打开写字板程序。

在客户端中,客户机通过undef方法取消本地的instance_eval方法,强制使DRbObject对象调用远程服务器的instance_eval。代码如下所示:

class << ro

  undef :instance_eval  #强制调用被传送到ro

end

如果远程对象是Unix,那么便可以很轻易地使用unlink等命令来删除Unix主机的文件。

通过设置$SAFE=1可以避免这种情况,但$SAFE=1会取消所有的eval相关操作。$SAFE操作主要可以禁止被污染(taint)的类或方法,共分5个级别,其高级应用不属于本书讲解范围,有兴趣的读者可以查阅相关资料。

11.5.2   访问控制列表

ACL(访问控制列表)类是用于DRb访问控制的模块,主要用于设置DRb的访问控制列表和一些权限。下面的例子对对acl.rb的使用进行简要的介绍。

1.服务器端

l         拒绝所有网段的连接请求。

l         允许本地主机,192.168.0.*192.168.1.*网段连接druby服务器。

l         设置安全标志$SAFE=1,以禁止eval及其相关功能。

l         提供hellosampletest?方法以响应客户端请求。

下面的代码是一个通过带有访问列表的DRb服务器应用示例,通过构筑和安装访问控制列表。

  #配置访问控制列表

  acl = ACL.new(%w(deny all

                   allow 192.168.1.*

                   allow 192.168.0.*

                   allow localhost))

  #安装访问控制列表

  DRb.install_acl(acl)

通过安装访问控制列表,DRb服务器拥有了对ACL中所注册的IP的访问控制权限,它可以允许和拒绝某些IP的连接请求,访问控制列表程序示例如下。

代码11.18 访问控制列表-服务器端:

#!/usr/bin/ruby

#服务器端

require 'drb'

require 'drb/acl'

 

#设置安全级别

$SAFE = 1

 

class AclDRb

    def initialize

        @hello = 'hello'

    end

 

    def hello

        @hello + ' '+Time.now.to_s

    end

 

    def sample(a, b, c)

        a.to_i + b.to_i + c.to_i

    end

 

    def test?

        info = Thread.current['DRb']

 

        p info['client'].peeraddr if info

        info['client'].peeraddr

    end

end

 

if __FILE__ == $0

 

  #配置访问控制列表

  acl = ACL.new(%w(deny all

                allow 192.168.1.*

                allow 192.168.0.*

                allow localhost))

 

  #安装访问控制列表

  DRb.install_acl(acl)

 

  DRb.start_service('druby://localhost:8886', AclDRb.new)

  puts DRb.uri

  DRb.thread.join

 

end

2.客户端

l         能够连接上服务器。

l         能够调用服务器端的hello方法返回一个“hello”字符串以测试服务器端是否能运行。

l         通过test?方法希望服务器能打印出其client socket的信息。

代码11.19 访问控制列表-客户端:

#!/usr/bin/ruby

#客户端

require 'drb'

ro = DRbObject::new_with_uri("druby://localhost:8886")

p ro.hello

p ro.test?

由于本例是在本机运行,因此不会受到访问控制列表的影响,如果需要测试,需要把上例中的localhost改为实际IP

11.6  分布式应用示例

11.6.1   分布式开发背景

本节通过实例介绍DRb的实际分布式应用,分布式系统就原型来说差异并不大,使用CORBA标准可以搭建分布式系统,使用SOAP也可以,而通过DRb则是最简单的一种方法。

它不同于CORBASOAP之类的标准协议,DRb只能用于RubyRuby也提供了CORBASOAP支持,但如果没有特殊需要,这些协议在用于DRb技术的平台上并无实际意义,因为使用Ruby进行跨平台的开发已经很简单了。

Google是世界上分布式计算最出名的公司,它通过搭建数十万台如图11.1所示的廉价个人PC,组成了世界上最大的分布式计算机集群,这些计算机组成的是一个完整的分布式文件系统,使它的业务处理速度非常快。这种分布式计算机集群的一个重要优势在于,计算和服务的分布化使单个PC的损毁对于整体的服务不会造成太大影响。

11.6.2   分布式集群原理

11.1展示了一个小型的分布式应用的原型。

11.1 分布式应用框架

这个应用框架实现了以下功能:

1.服务器端所提供的服务

l         启动ServObj提供分布式Ruby服务

l         读取需要处理的节点

l         返回给客户端未处理的节点信息

l         通过recalc提供异常处理恢复

l         打印日志

2.客户端执行任务的流程

1)连接DRb服务器。

2)得到一个任务节点号,如果节点号小于0则退出。

3)通过这个任务节点号,从存储器得到需要处理的数据并且进行处理。

4)处理完成并且将事务提交之后,向服务器提交确认信息,通知服务器从挂起任务节点号表中删除这个节点。

5)通知服务器打印日志。

6)跳至步骤2

11.6.3   分布式应用框架

本节介绍的分布式应用框架是分布式应用的基础框架,该框架主要分为3个模块,即drbserver.rb(计算服务调度者)、drbclient.rb(计算能力提供者)和analyze.rb(分析模块)。

1.服务器计算调度者

计算服务调度者提供DRb服务和向节点计算机提供待处理的计算节点。

代码11.20  分布式应用实例-服务器:

#!/usr/bin/ruby

require 'drb'

#超时时间,如果一个节点被计算了2小时仍然没返回,则认为它已经死亡

timeout=7200

 

class ServObj

      attr_accessor :nodes,

      :usingnodes

 

#初始化服务器对象,读取待处理的节点

      def initialize

            @nodes=[]

            readnode

            @hashused={}

      end

 

#从配置文件读取节点信息

      def readnode

            open("#{Dir.pwd}/nodes.config") do |f|

                  @nodes = f.read().split(' ')

            end

      end

 

#把未处理完的节点保存回配置文件

      def savenode

            open("#{Dir.pwd}/nodes.config",'w+') do |f|

                  f.write(@nodes.join(' '))

                  f.flush

                  f.close

            end

      end

 

#读取下一个空闲节点

      def get_nextnode

            nid = @nodes.shift

            if nid

                  @hashused[nid] = Time.now

                  nid

            else

                  -1

            end             

      end

 

#某个节点的操作工作已经完成

      def finish(nid)

            if @hashused.has_key?(nid)

                  @hashused.delete(nid)

                  arr =[]

                  arr +=  @nodes

                  arr += @hashused.keys

                  arr.sort

            else

                  @nodes.delete(nid)

            end

      end

 

#提交给本机监控程序

#判断是否可以关闭服务器

#如果运算完毕,则关闭服务器

      def needclose?

            p @nodes

            p @hashused

            if @nodes.empty? && @hashused.empty?

                  true

            else

                  false

            end

      end

 

#由于部分节点的操作可能因为客户机以外而终结

#recalc对这些未完成的事务进行部分的保证

      def recalc

            @hashused.delete_if do |key,value|

                  if key-value > timeout

                        @nodes << key

                  end

                  key-value > timeout

            end

      end

 

#日志打印

      def log(message)     

            File.open("#{Dir.pwd}/log.log", "a") do |f|     

                  f.write("#{Time.now}: #{@name}: #{message}")         

            end

      end

 

      def get_time

              Time.now.strftime("%Y-%m-%d %H:%M:%S")

      end

end

 

if __FILE__ == $0

      serv = DRb.start_service("druby://localhost:2222", ServObj.new)

      Thread.new{

            serv.thread.join

      }

 

#定时轮循,看是否可以关闭服务

      Thread.new{

            while true do

                  if serv.front.needclose?

                        serv.stop_service

                        break

                  end

                  sleep 3

            end

      }.join

end

 

2.客户端计算能力提供者

计算能力提供者提供计算能力的客户端。下面是它的实现代码。

代码11.21 分布式应用实例-客户端:

#!/usr/bin/ruby

require 'drb'

require 'analyze'

 

if __FILE__ == $0

      DRb.start_service

      dServ= DRbObject.new(nil, "druby://localhost:8880")

      p dServ.class

      begin

            while true

                  nid = dServ.get_nextnode

                  if nid.to_i > 0                 

                        AnalyzeData(nid)

                        dServ.finish(nid)

 

                        log= "task #{nid} finished at server time:#{dServ.get_time}" <<"/n"

                        dServ.log(log)

                        $stdout << log

                  else

                        break

                  end                 

            end

      rescue DRb::DRbError =>e

            puts "a serious error accur"

            puts "error string:/n#{e.message.class}"

            system("pause")

      ensure

      end

end

3.数据处理模块

分析模块进行数据分析和处理的模块。本例中的分析只进行了简单地打印字符串,并且使主线程休眠1秒钟的操作,这样是为了能够让两个客户机的操作顺序更加明显。

代码11.22 分布式应用实例-客户端分析模块:

#!/usr/bin/ruby

def AnalyzeData(nid)

      puts 'analyze...'

      sleep 1

end

 

if __FILE__ == $0

      AnalyzeData(0)

end

11.2~图11.4列出了程序执行情况。

 

11.2  drbserv.rb的运算                     11.3  drbclient.rb的第1个实例

11.4  drbclient.rb的第2个实例

日志文件“log.log”列出了该应用所有任务的执行情况。

Tue Jun 11 23:03:59 China Standard Time 2006: : task 1 finished at server time:2006-07-11 23:03:59

Tue Jun 11 23:04:00 China Standard Time 2006: : task 2 finished at server time:2006-07-11 23:04:00

Tue Jun 11 23:04:00 China Standard Time 2006: : task 3 finished at server time:2006-07-11 23:04:00

Tue Jun 11 23:04:01 China Standard Time 2006: : task 4 finished at server time:2006-07-11 23:04:01

Tue Jun 11 23:04:01 China Standard Time 2006: : task 5 finished at server time:2006-07-11 23:04:01

………….

Tue Jun 11 23:04:08 China Standard Time 2006: : task 17 finished at server time:2006-07-11 23:04:08

Tue Jun 11 23:04:08 China Standard Time 2006: : task 18 finished at server time:2006-07-11 23:04:08

Tue Jun 11 23:04:09 China Standard Time 2006: : task 19 finished at server time:2006-07-11 23:04:09

Tue Jun 11 23:04:09 China Standard Time 2006: : task 20 finished at server time:2006-07-11 23:04:09

 

 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值