Introduction
This article describes how a distributed database management system was implemented. The system contains main server-LookupServer, several DatabaseServers and Clients. Using the classes available, applications can be created with any number of DataBaseServers and Clients.
The application uses sockets from java.nio
for communicating:
- LookupServer<->Databaseservers
- Databaseservers<->Databaseservers
- Clients<->Databaseservers
Background
The basic concepts needed to understand this code are about sockets. A socket is an end-point of a bidirectional process-to-process communication across an IP based network. A socket provides an interface between a process, thread or application and the TCP/IP protocol stack provided by the operating system. Sockets can be blocking and nonblocking. A blocking socket, as the name says, will block when sending or receiving a message. A non blocking socket, when wishing to receive a message will check if the message is available; if so, it will read it, otherwise it will continue doing something else.
Using the Code
In order to compile and run the code, I used ant. In the build.xml file, I defined targets for compiling all the source code and producing a jar file. In order to run the application for a certain test, I introduced a runTest1
target.
Let me explain the main classes contained in the project.
1. Client
The Client
class defines a transaction in the database. All the actions of a transaction must keep the database’s integrity. The changes made by a client will only be available to that particular client until it commits the data. Each transaction will end with a commit or rollback command. Choosing to commit will save the changes made by the transaction making them available to other clients (transactions), while choosing rollback will cancel all the changes made till that point.
The behavior of a client is defined by the following pattern. First it will connect to a database server, which will assign it a unique TID (transaction id). After receiving the TID, the client can proceed in using the object database available. Thus, it will send several requests to identify the values of certain objects or to modify them. When the transaction is finished, the client will either commit the data or rollback.
The Client
class implements TransactionInterface
, which defines the following functions:
public String begin(String DatabaseServerIP, int DatabaseServerPort)
public int getValue(String objName);
public void setValue(String objName, int value);
public void commit();
public void rollback();
begin
: The client will connect to the database server defined by the parameters: DatabaseServerIP
and DatabaseServerPort. getValue
: The client will request the value of a certain object from the DBServer. The object need not be stored on that server, it will be up to it to find the object’s location and provide the client with its value. setValue
: The client will announce to the server that it wishes to change the value of a certain object. The changes will not be seen by other DBServers and clients until the client chooses to commit the new data. commit
: The client will send a request to the DBServer to commit all the data that was worked on, then it will disconnect from the server. It is the database server’s job to announce to all the other database servers about the changes made. rollback
: The client will announce to the server that it wishes to cancel all changes made, then it will disconnect from the server.
2. DatabaseServer
There are several database servers, each storing a number of objects. An object is defined by a name which is unique within a group of database servers, and its values. The class used to define such an object is DatabaseObject
which I shall discuss later. A set of such database servers will connect to a lookup server, that will have intelligence about all the existing data servers and their stored objects.
The DatabaseServer
class extends Thread
and implements DatabaseServerInterface
, which defines the following functions:
public void startServer(String myIP, int myPort, String LookupServerIP,
int LookupServerPort);
public void addObject(String objName, int initialValue);
startServer
: This function will open a connection on which it will listen for requests from clients or other DBServers. It will also store the Lookup server information (its IP and port given as parameters) for future information requests it will make. addObject
: The DBServer will store information about a certain object (its name and value) and it will announce the LookupServer
about its existence. stopServer
: The DBServer will wait a predefined time for the current tasks to finish, then it will close all connections.
Each database server is a thread and it will run in the background after it is started, awaiting requests. In order to process several incoming requests, a database server uses a pool of threads. Each request received will be assigned to a thread in the pool, if one is available; it will wait for an available one otherwise. A task assigned to a thread in the pool is defined by the internal class HandlerData
. According to the type of message received, it will accept a connection or read, interpret data and send an answer.
3. LookupServer
The Lookup server is the central server, in charge of managing the other servers and offering them information about each other. Multiple Lookup servers could be defined as well, but each will be responsible for its own set of Database Servers.
The LookupServer
Class extends the Thread
class and implements the LookupServerInterface
which defines the following functions:
public void startServer(String myIP, int myPort);
public void stopServer();
startServer
: This function will open a connection on which the server will listen for requests from DBServers. stopServe
r
: The server will wait a predefined time for the current tasks to finish, then it will close all connections.
Just like the DBServers the lookup server can answer multiple requests. For this, it uses a pool of threads. Once a request is received, a thread from the pool is assigned to complete the task of processing it. The task assigned to this thread is defined by the internal class HandlerLookup
. According to the type of message received, it will accept a connection or read, interpret data and send an answer if one is required.
4. DatabaseObject
The DatabaseObject
class defines an object stored on a database server. The important information it stores consists of the object name and its value.
5. TestScenario1
TestScenario1
is a test which shows the basic features of the classes implemented. It defines a lookup server, two database servers and three clients and checks the database integrity and the validity of the client operations.
Points of Interest
The present project offers a system using distributed databases of simple objects, with good communications among the parts involved. However it leaves room for improvement. The most important one is that the DBServer should inform the lookup server about exiting in order for the lookup to remove the entries about it from its own database. Another improvement would be defining the protocol between the separate entities (clients, dbservers and lookupserver) within a separate class making it more flexible.
An important problem I should mention here involves the use of the selector (when using nonblocking sockets). In this project, the fact that multiple threads act on the same data, synchronization is vital. Most importantly when using a selector, the main thread will block on the select
method so no one else could add a new socket channel to the selector until an incoming connection releases it. Even then, it cannot be sure that the thread trying to add a new socket channel to the selector will get the chance to do so before the main thread blocks on the select
method again. To solve this problem, I used synchronization on an object: sinc
. When a worker thread wishes to register a new channel to the selector, it first takes ownership of the object sinc
then "wakes up" the selector. After registering the channel, it releases the object sinc
.
synchronized(sinc)
{
this.selector.wakeup();
sChannel.register(this.selector, SelectionKey.OP_READ);
}
In the mean time, the main thread will exit from the select
function with a return code 0
. The code returned is tested and in case it is 0
, an attempt to take ownership of sinc
is made. Until the worker thread finishes, the main thread will not be able to enter the synchronized section, thus allowing registering a channel without interference.
while (true)
{
try
{
a = selector.select();
if(a == 0)
{
synchronized(sinc)
{}
continue;
}
}
}
References and Thanks
This project was originally developed as an assignment for the Languages for Distributed Programming course of the University Politehnica of Bucharest. Special thanks to the teaching assistant who proposed this assignment as it has been one of the most complex and interesting assignments given.
History
- 5th December, 2008: Initial post
About Myself
I am currently a student in my final year at the University Politehnica of Bucharest. My main interest involves distributed systems and parallel architectures but I enjoy being involved in other challenging projects as well. In my spare time, I write on a blog about interfete web (web interfaces).