When reviewing all of our DRb applications, you will notice, for a start, that we hardcoded IP addresses and ports into both our servers and clients.
what do you do if you need to start the service on a different machine that has a different IP address? We could also create and attempt to maintain complex configurations, but in the modern world of cloud computing IP addresses fluctuate every time you launch an instance.
What about finding out what services, as a client, are available in our world of distributed applications? Again, we have been hardcoding our connection to the service we want to use.
Enter Rinda, a Ruby port of the Linda1 distributed computing paradigm. This paradigm allows an application, through the use of a RingServer, to detect and interact with other services on the network. This should help us solve some of the problems we were facing with our earlier DRb applications.
"Hello World" the Rinda Way
A RingServer is essentially a central clearinghouse for services,The life cycle for a typical RingServer with one service and one client basically goes something like this: The client asks the RingServer where to find a particular service. The RingServer responds with the service’s address. The client and service then communicate directly with each other.
Building a RingServer is straightforward and simple. In Chapter 3,“RingyDingy,” we’ll discuss RingyDingy, a library that makes it even easier to use RingServers; but first we should write our own so we can understand exactly what it does.
TupleSpaces and Tuples: We can think of a TupleSpace as that bulletin board. It is a place where we can post our services. The flyer is like the Tuple.
If they find a posting they like, they can either take it off the board so that no one else will see it, or they can make a copy for themselves and leave the original up there for others to see.
In our current example we would be posting a “Hello World”ervice Tuple to the TupleSpace.
server
client
index 2: After we have retrieved a Tuple from the RingServer, we need to get the HelloWorldServer instance out of the Tuple Array with server = service[2]. It might seem odd that in order to retrieve our HelloWorldServer instance from the Tuple we retrieved from the RingServer using an Array syntax, service[2], it does, in fact, make sense. We stored an Array containing the original description of the service and the instance of the HelloWorldServer in the RingServer as an Array, the index of the HelloWorldServer is 2.
output
You can also see that the “Hello World” server is running on 192.168.80.172:53062. That was the IP address of the machine running the server, and the port was picked basically at random.
Understanding Tuples and TupleSpaces
TupleSpaces allow us to perform a variety of tasks. We can write to a TupleSpace to post our service. Someone can come along and get a read-only copy of a service from that TupleSpace.
Writing a Tuple to a TupleSpace
Createing a Tuple: pass in an Array containing the template you want to store and When you are trying to look up a service, you must match the template.
there does seem to be a loose “standard” in the community of using a fourentry Tuple template with the following pattern: [:name, :Class, instance_of_class, ‘description‘].
We would write that template like so:
Under the covers, Rinda takes the Array and creates the appropriate Rinda::Tuple class, so we don’t have to worry about learning yet another class API.
The write method also takes an optional second argument, which can be either an Integer, representing the number of seconds you want this Tuple to live, or a Renewer.
The second parameter to the write method determines how and when the Tuple gets expired.
Reading a Tuple from a TupleSpace
In our “Hello World” application server code, we called the write method on the RingServer, passing it a Tuple template. To retrieve that Tuple we call the read method and pass it a variation of our Tuple template. Because our original Tuple template had four entries, our client also has to pass in a Tuple template with four entries. When you pass in nil as an entry in a Tuple template, you’re doing a wildcard search on any entry in that position.
In our case we are asking for the first service whose first Tuple entry is :hello_world_service. We don’t care what the other template entries are.
example:
remerber to start tuplespace service
first start DRb.thread.join, then comment it, and run the read code.
output:
As you can see, we make three different requests for services, each with different Tuple templates. Yet each time we get back a reference to the first Tuple entry we created. When Rinda attempts to find a Tuple based on a Tuple template, it returns the first Tuple that matches the Tuple template passed into it. This should demonstrate the importance not only of using a consistent Tuple template, but also of writing your Tuple to be as specific as possible.
Taking a Tuple from a TupleSpace
let’s see what happens when we throw a bit of concurrency into the mix.
a fairly contrived example :
server:
client:
In our client we will create ten different Threads. Each of those Threads reads the current Integer from the server. The client then sleeps for a random time between 0 and 5 seconds. When the client wakes up, it increments the Integer by 1 and writes that back to the server. Because we have ten Threads running, and each Thread loops through our code ten times, we should see the final count be 100. But output:
...
Well, each time a Thread reads the Integer it got from the service, it holds onto that Integer,sleeps for a bit, and then updates that Integer and writes it back to the service.When it does that, it overwrites the previous Integer that another Thread placed in there while the first thread was sleeping.
Fix it:
Change ring_server.read to ring_server.take
When you use the take method, each thread takes the Tuple from the server,holds it while it sleeps, and then updates it and puts it back, it prevents the other threads from doing the same thing.
out: #用sublime运行的结果有点问题,用cmd跑了下
The question you should be asking is, if two Threads hit the service, and the first one calls the take method, gets the Tuple, and then sleeps, why doesn’t the other raise an exception? Shouldn’t the second Thread get back nil from the server, causing the rest of the code to blow up? The answer is, it does not get back nil from the server. In fact, what actually happens is that the second Thread just sits there and waits for the take method to return the Tuple. The take method is basically wrapped in a big infinite loop that keeps checking to see if the Tuple has been returned. When it is returned, the loop is broken, and the Tuple is returned to the client. In the real world, this code would hang forever while it waited for the Tuple if we never wrote it back. So it is wise to always wrap any take method calls in a Timeout to prevent your code from simply hanging.
Reading All Tuples in a TupleSpace
The read method returns the first Tuple that matches the template
If you want to get back a list of services,the solution in Rinda is to use the read_all method.