Rinda::Ring allows DRb services and clients to automatically find each other without knowing where they live.
DRb servers register themselves with a RingServer which allows clients to find the servers they need. Many servers may register themselves with the RingServer. The DRb servers don't need to run on the same machine.
To use Rinda::Ring you'll need Ruby 1.8.1 or newer, or drb 2.0.3 or newer.
A RingServer holds a TupleSpace that is used a a naming system for distributed services. Here's a simple ring server:
#!/usr/bin/env ruby -w
# ringserver.rb
# Rinda RingServer
require 'rinda/ring'
require 'rinda/tuplespace'
# start DRb
DRb.start_service
# Create a TupleSpace to hold named services, and start running
Rinda::RingServer.new Rinda::TupleSpace.new
# Wait until the user explicitly kills the server.
DRb.thread.join
Go ahead and run this script in a shell, and leave it alone. Now services can register themselves with the RingServer for clients to find.
To register a service, a client first looks for the RingServer, then adds a tuple describing the service to it. In this example, we'll provide a TupleSpace that clients can use to communicate.
A RingServer registration is a tuple and a renewer.
The renewer specifies how often the provided service should be checked to see if it is still alive. Rinda provides a SimpleRenewer:
renewer = Rinda::SimpleRenewer.new
The registration tuple is formatted like this:
[:name, class, a DRbObject, note ]
For our TupleSpace example, the tuple we'll use to register the TupleSpace will look like this:
tuple = [:name, :TupleSpace, example_ts, 'TupleSpace']
To register the TupleSpace, we need to know where the RingServer lives. Typically when working with DRb, you must provide a name for the server or use the one which DRb generates when you start it. If you look at the RingServer example above, you'll notice that we didn't do that. RingServers listen on a UDP socket, so they can be found by Rinda::RingFinger.
Here's how you use Rinda::RingFinger to find a RingServer:
require 'rinda/ring'
# start DRb
DRb.start_service
ring_server = Rinda::RingFinger.primary
Now ring_server can be used to register a service, which we like this:
ring_server.write(tuple, renewer)
Fortunately, Rinda::Ring provides a simple way of providing services, Rinda::RingProvider. You simply need to provide the class, object, description, and an optional renewer. Let's put it all together:
#!/usr/bin/env ruby -w
# rinda_ts.rb
# Registering a TupleSpace with Rinda::Ring
require 'rinda/ring'
require 'rinda/tuplespace'
DRb.start_service
ts = Rinda::TupleSpace.new
provider = Rinda::RingProvider.new :TupleSpace, ts, 'Tuple Space'
provider.provide
DRb.thread.join
Go ahead and run this script in a shell and leave it alone.
So now we should have a TupleSpace that anybody can find via the RingServer. Let's test to see if it got exported correctly by seeing what's registered on the RingServer.
Since the RingServer uses a TupleSpace to hold the the registered Services, we can search it for registered services. Going back to the example of how to find a RingServer, we'll ask the RingServer to find all of the services it knows about.
To search the RingServer's TupleSpace, we use a template that has the same length as the registration tuples, and starts with :name, like this:
ring_server.read_all [:name, nil, nil, nil]
This returns all tuples that start with :name and have anything in the last 3 fields. To put it all together:
#!/usr/bin/env ruby -w
# list_services.rb
# List all of the services the RingServer knows about
require 'rinda/ring'
DRb.start_service
ring_server = Rinda::RingFinger.primary
services = ring_server.read_all [:name, nil, nil, nil]
puts "Services on #{ring_server.__drburi}"
services.each do |service|
puts "#{service[1]} on #{service[2].__drburi} - #{service[3]}"
end
Running this will give a result similar to the following:
Services on druby://localhost:4572 TupleSpace on druby://localhost:4573 - Tuple Space
Now that we've verified that the service is registered, we can modify some of the DRb examples to find each other via the RingServer. Since we've already got a TupleSpace registered, I'll use rindas.rb and rindac.rb from the Rinda examples.
For rindas.rb, remove the "uri =" and "ts =" lines, then after DRb.start_service add:
ring_server = Rinda::RingFinger.primary
# Fetch the reference to the remote TupleSpace
rts = ring_server.read([:name, :TupleSpace, nil, nil])[2]
ts = Rinda::TupleSpaceProxy.new rts
rindas.rb will end up looking like this:
#!/usr/bin/env ruby -w
# rindas.rb
# rindas modified to find the TupleSpace via a RingServer
require 'rinda/ring'
def do_it(v)
puts "do_it(#{v})"
v + v
end
DRb.start_service
ring_server = Rinda::RingFinger.primary
ts = ring_server.read([:name, :TupleSpace, nil, nil])[2]
ts = Rinda::TupleSpaceProxy.new ts
loop do
r = ts.take(['sum', nil, nil])
v = do_it(r[2])
ts.write(['ans', r[1], r[2], v])
end
Do the same for rindac.rb, so you end up with:
#!/usr/bin/env ruby -w
# rindac.rb
# rindac.rb modified to find the TupleSpace via a RingServer
require 'rinda/ring'
def do_it(v)
puts "do_it(#{v})"
v + v
end
DRb.start_service
ring_server = Rinda::RingFinger.primary
ts = ring_server.read([:name, :TupleSpace, nil, nil])[2]
ts = Rinda::TupleSpaceProxy.new ts
(1..10).each do |n|
ts.write(['sum', DRb.uri, n])
end
(1..10).each do |n|
ans = ts.take(['ans', DRb.uri, n, nil])
p [ans[2], ans[3]]
end
If you run rindas.rb and rindac.rb in separate shells, you should get the following output:
rindas.rb:
do_it(1) do_it(2) do_it(3) do_it(4) do_it(5) do_it(6) do_it(7) do_it(8) do_it(9) do_it(10)
rindac.rb:
[1, 2] [2, 4] [3, 6] [4, 8] [5, 10] [6, 12] [7, 14] [8, 16] [9, 18] [10, 20]
And that's all there is to Rind::Ring!
This explanation of Rinda::Ring was created from the Masatoshi SEKI's Japanese Rinda::Ring page after translating it to English, Masatoshi's rinda examples in Ruby's sample directories, and reading the Rinda sources.
© Eric Hodel - 20040304