├── Client.java ├── LBRequestServer.java ├── LoadBalancer.java ├── README.md ├── Worker.java ├── WorkerInfo.java ├── WorkerTask.java ├── jars ├── json-20180813.jar └── mysql-connector-java-8.0.15.jar ├── launch_all.sh ├── students.sql └── worker_list.txt /Client.java: -------------------------------------------------------------------------------- 1 | import org.json.JSONObject; 2 | 3 | import java.io.*; 4 | import java.net.Socket; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.Random; 7 | 8 | public class Client { 9 | 10 | public static void main(String[] args) { 11 | try { 12 | while (true){ 13 | 14 | // Open connection to Load Balancer. 15 | Socket loadBalancerSocket = new Socket("localhost", 12345); 16 | 17 | // Start a new thread to send request. 18 | Thread requestSender = new Thread(new RequestSender(loadBalancerSocket)); 19 | requestSender.start(); 20 | // To clearly observe print statements on Client, Load Balancer and Workers: 21 | // Thread.sleep(2000); 22 | } 23 | } catch (Exception e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | } 28 | 29 | class RequestSender implements Runnable{ 30 | private Socket loadBalancerSocket; 31 | RequestSender(Socket loadBalancerSocket){ 32 | this.loadBalancerSocket = loadBalancerSocket; 33 | } 34 | @Override 35 | public void run() { 36 | try { 37 | BufferedWriter lbWriter = new BufferedWriter(new OutputStreamWriter(loadBalancerSocket.getOutputStream(), StandardCharsets.UTF_8)); 38 | BufferedReader lbReader = new BufferedReader(new InputStreamReader(loadBalancerSocket.getInputStream(), StandardCharsets.UTF_8)); 39 | 40 | // Get a random Student ID in range [1, 7](The number of rows). 41 | int sid = new Random().nextInt(7) + 1; 42 | 43 | // Send to Load Balancer. 44 | lbWriter.write(sid + "\n"); 45 | lbWriter.flush(); 46 | 47 | // Get worker's response, sent via Load Balancer. 48 | String jsonString = lbReader.readLine(); 49 | JSONObject json = new JSONObject(jsonString); 50 | String result = "Information received for Student with ID="+sid+":"+ 51 | "\nName: "+json.getString("name")+ 52 | "\nDate of Birth: "+json.getString("dob")+ 53 | "\nMajor of Study: "+json.getString("major")+ 54 | "\nEducation Level: "+json.getString("level")+ 55 | "\nYear of Study: "+json.getString("year"); 56 | System.out.println(result+"\n\n"); 57 | 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /LBRequestServer.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.net.Socket; 3 | import java.nio.charset.StandardCharsets; 4 | 5 | public class LBRequestServer implements Runnable{ 6 | private Socket clientSocket, workerSocket; 7 | private WorkerLoads workerLoads; 8 | private int currentServer; 9 | LBRequestServer(Socket clientSocket, Socket workerSocket, WorkerLoads workerLoads, int currentServer){ 10 | this.clientSocket = clientSocket; 11 | this.workerSocket = workerSocket; 12 | this.workerLoads = workerLoads; 13 | this.currentServer = currentServer; 14 | } 15 | 16 | @Override 17 | public void run() { 18 | try { 19 | BufferedWriter workerWriter = new BufferedWriter(new OutputStreamWriter(workerSocket.getOutputStream(), StandardCharsets.UTF_8)); 20 | BufferedWriter clientWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream(), StandardCharsets.UTF_8)); 21 | BufferedReader workerReader = new BufferedReader(new InputStreamReader(workerSocket.getInputStream(), StandardCharsets.UTF_8)); 22 | BufferedReader clientReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8)); 23 | 24 | // Take request from client and forward to selected worker. 25 | workerWriter.write(clientReader.readLine()+"\n"); 26 | workerWriter.flush(); 27 | 28 | // Take response from selected worker and send to client. 29 | clientWriter.write(workerReader.readLine()+"\n"); 30 | clientWriter.flush(); 31 | 32 | workerSocket.close(); 33 | clientSocket.close(); 34 | 35 | // Request processed, decrement load of selected worker. 36 | workerLoads.decrementLoad(currentServer); 37 | 38 | }catch (IOException e) { 39 | e.printStackTrace(); 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LoadBalancer.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.net.ServerSocket; 3 | import java.net.Socket; 4 | import java.util.ArrayList; 5 | 6 | public class LoadBalancer { 7 | private static void startLoadBalancer(String schedAlgo) { 8 | try { 9 | // List of WorkerInfo objects. WorkerInfo class has two fields: host, port. 10 | ArrayList workers = new ArrayList<>(); 11 | BufferedReader workerFile = new BufferedReader(new FileReader(new File("worker_list.txt"))); 12 | 13 | // Populate worker list from worker_list.txt. 14 | while (workerFile.read() != -1) { 15 | String[] info = workerFile.readLine().split(","); 16 | workers.add(new WorkerInfo(info[0], Integer.valueOf(info[1]))); 17 | } 18 | 19 | // WorkerLoads object consists of a list of worker loads, one int load for each worker. 20 | WorkerLoads workerLoads = new WorkerLoads(workers.size()); 21 | 22 | // Open Load Balancer Socket. This socket acts as a single entry point for all incoming request from Clients. 23 | ServerSocket balancerSocket = new ServerSocket(12345); 24 | int currentWorker = 0; 25 | while (!Thread.interrupted()) { 26 | 27 | // Accept a new client connection. 28 | Socket clientSocket = balancerSocket.accept(); 29 | if (schedAlgo.equals("RR")) { 30 | // When Round Robin" selected, select Workers in a circular fashion. 31 | currentWorker = (currentWorker + 1) % workers.size(); 32 | System.out.println("Selected worker " + currentWorker + "."); 33 | } 34 | else if (schedAlgo.equals("LC")) { 35 | // When Least Connections selected, select Worker with least active connections/requests, and 36 | // increment its load. 37 | currentWorker = workerLoads.getMinLoadServer(); 38 | int currLoad = workerLoads.getLoad(currentWorker); 39 | System.out.println("Selected worker " + currentWorker + " with load: " + currLoad + "."); 40 | workerLoads.incrementLoad(currentWorker); 41 | } 42 | 43 | // Open connection to selected worker. 44 | Socket workerSocket = new Socket(workers.get(currentWorker).getHost(), workers.get(currentWorker).getPort()); 45 | 46 | // Start a new thread to serve this request. 47 | Thread lbRequestServer = new Thread(new LBRequestServer(clientSocket, workerSocket, workerLoads, currentWorker)); 48 | lbRequestServer.start(); 49 | } 50 | 51 | } catch (IOException e) { 52 | e.printStackTrace(); 53 | } 54 | } 55 | 56 | public static void main(String[] args) { 57 | 58 | // args[0] has the scheduling algorithm parameter(RR and LC, for Round-Robin and Least-Connections respectively). 59 | String schedAlgo = args[0]; 60 | startLoadBalancer(schedAlgo); 61 | } 62 | } 63 | 64 | 65 | class WorkerLoads { 66 | private ArrayList workerLoads = new ArrayList<>(); 67 | 68 | WorkerLoads(int num_servers) { 69 | // Initialize loads of all workers to 0. 70 | for (int i = 0; i < num_servers; i++) 71 | workerLoads.add(0); 72 | } 73 | 74 | int getLoad(int index){ 75 | return workerLoads.get(index); 76 | } 77 | 78 | // Find worker with minimum load. 79 | synchronized int getMinLoadServer() { 80 | int minLoad = workerLoads.get(0), min_ind = 0; 81 | for (int i = 1; i < workerLoads.size(); i++) { 82 | int thisLoad = workerLoads.get(i); 83 | if (thisLoad < minLoad) { 84 | minLoad = thisLoad; 85 | min_ind = i; 86 | } 87 | } 88 | return min_ind; 89 | } 90 | 91 | synchronized void incrementLoad(int index){ 92 | workerLoads.set(index, workerLoads.get(index) + 1); 93 | } 94 | 95 | synchronized void decrementLoad(int index){ 96 | workerLoads.set(index, workerLoads.get(index) - 1); 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Application-level Load Balancer in Java # 2 | This project implements a simple load balancing program written in Java, with a Client and a Server program demonstrating its functionality. The functionality is as below: 3 | * All clients connect to the Load Balancer, which is a single entry point for all requests. The requests consist of a string- an ID- the data corresponding to which is to be obtained from Workers and displayed on terminal. 4 | * The Load Balancer accepts requests from clients and redirects them to appropriate workers selected using Round-Robin or Least-Connections scheduling, whichever is specifed by user via command-line. 5 | * The Workers(Servers) extract information corresponding to recieved requests(IDs) from a MySQL Database, and return the data in the form of JSON responses to the Load Balancer, which forwards it to the clients as mentioned above. 6 | * The Client, Load Balancer and Workers are all Multithreaded, and hence capable of Sending/Serving multiple requests at once. 7 | 8 | #### To Dos: #### 9 | * Add multiple, more complex requests/tasks. 10 | * Add functionality to forward requests to workers based on their priority(high/medium/low). 11 | ## Steps to replicate for anyone interested: ## 12 | * Prerequisites: 13 | * JDK 8 or above 14 | * MySQL Server(+ MySQL Workbench recommended) 15 | * Necessary Jar files(included in /jars/ directory) 16 | * Import students.sql file to replicate the schema and table used with project. If using MySQL Workbench: Server->Data Import-> Import from Self-Contained file. 17 | * The hostname:port pairs are extracted from worker_list.txt, modify if needed. The scheduling algorithm is given as a command-line argument to LoadBalancer.class file. To launch all processes: 18 | * If using a linux distro with gnome-terminal, just execute the included bash script launch_all.sh. 19 | * If using other terminal(ex. Xterm, Konsole), replace "gnome-terminal" with "terminal_name" everywhere(should work with most terminals). 20 | * If not using linux, manually copy and paste all javac and java commands from launch_all.sh into terminal. Note: the port numbers must match those in worker_list.txt. 21 | * In all cases, modify the command-line argument for LoadBalancer as needed. Valid values: "RR" for Round-Robin and "LC" for Least Connections. 22 | * Once launched, windows corresponding to Workers, Load Balancer, and Client will show appropriate outputs on terminal. 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Worker.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | import java.net.ServerSocket; 3 | import java.net.Socket; 4 | import java.sql.*; 5 | 6 | public class Worker { 7 | public static void main(String[] args) { 8 | try { 9 | // Load MySQL JDBC Driver. 10 | Class.forName("com.mysql.cj.jdbc.Driver"); 11 | Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/students", "root", "xxxxxx"); 12 | 13 | // Open socket for this worker. 14 | ServerSocket workerSocket = new ServerSocket(Integer.valueOf(args[0])); 15 | while(true){ 16 | // Accept connection from Load Balancer. 17 | Socket loadBalancerSocket = workerSocket.accept(); 18 | 19 | // Start a new thread to service this request. 20 | Thread workerTask = new Thread(new WorkerTask(loadBalancerSocket, conn)); 21 | workerTask.start(); 22 | } 23 | } catch (IOException | ClassNotFoundException | SQLException e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WorkerInfo.java: -------------------------------------------------------------------------------- 1 | public class WorkerInfo { 2 | private int port; 3 | private String host; 4 | WorkerInfo(String host, int port){ 5 | this.host = host; 6 | this.port = port; 7 | } 8 | String getHost(){ 9 | return host; 10 | } 11 | int getPort(){ 12 | return port; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /WorkerTask.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.net.Socket; 3 | import java.nio.charset.StandardCharsets; 4 | import java.sql.Connection; 5 | import java.sql.ResultSet; 6 | import java.sql.SQLException; 7 | import java.sql.Statement; 8 | import org.json.JSONObject; 9 | 10 | public class WorkerTask implements Runnable { 11 | // Table column names. 12 | private static String[] columns = {"name", "dob", "major", "level", "year"}; 13 | private Socket loadBalancerSocket; 14 | private Connection conn; 15 | WorkerTask(Socket loadBalancerSocket, Connection conn){ 16 | this.loadBalancerSocket = loadBalancerSocket; 17 | this.conn = conn; 18 | } 19 | 20 | @Override 21 | public void run() { 22 | try { 23 | BufferedWriter lbWriter = new BufferedWriter(new OutputStreamWriter(loadBalancerSocket.getOutputStream(), StandardCharsets.UTF_8)); 24 | BufferedReader lbReader = new BufferedReader(new InputStreamReader(loadBalancerSocket.getInputStream(), StandardCharsets.UTF_8)); 25 | 26 | // Get student ID sent by client(via Load Balancer). 27 | String sid = lbReader.readLine(); 28 | 29 | // Query student data from database. 30 | String query = "SELECT name, dob, major, level, year FROM studentinfo WHERE sid="+sid; 31 | Statement stmt = conn.createStatement(); 32 | ResultSet rs = stmt.executeQuery(query); 33 | rs.first(); 34 | 35 | // Create json from data. 36 | JSONObject json = new JSONObject(); 37 | for(int i=0; i<5; i++) 38 | json.put(columns[i], rs.getString(i+1)); 39 | rs.close(); 40 | System.out.println("Sending info for Student with ID: "+sid); 41 | 42 | // Send json response to load balancer. 43 | lbWriter.write(json.toString()+"\n"); 44 | lbWriter.flush(); 45 | 46 | } catch (IOException | SQLException e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jars/json-20180813.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aayushARM/load-balancing-java/18bf328e38e6a605fbecf573c7af1fc277e7fb84/jars/json-20180813.jar -------------------------------------------------------------------------------- /jars/mysql-connector-java-8.0.15.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aayushARM/load-balancing-java/18bf328e38e6a605fbecf573c7af1fc277e7fb84/jars/mysql-connector-java-8.0.15.jar -------------------------------------------------------------------------------- /launch_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Compiling files..." 4 | javac -cp ./jars/json-20180813.jar:./jars/mysql-connector-java-8.0.15.jar *.java 5 | 6 | echo "Starting Worker processes..." 7 | count=0 8 | while IFS=, read _ port;do 9 | gnome-terminal --title="Worker $count" -e "bash -c 'java -cp ./jars/json-20180813.jar:./jars/mysql-connector-java-8.0.15.jar:./ Worker $port'" 1>/dev/null 10 | count=$((count+1)) 11 | done < worker_list.txt 12 | 13 | # Note: Small delay needed so as to let Workers launch completely and open Server Sockets. 14 | sleep 0.5s 15 | 16 | echo "Starting Load Balancer process..." 17 | #Commandline parameter options for LoadBalancer: "RR" for Round Robin scheduling, "LC" for Least Connections scheduling. 18 | gnome-terminal --title="Load Balancer" -e "bash -c 'java -cp ./jars/json-20180813.jar:./jars/mysql-connector-java-8.0.15.jar:./ LoadBalancer LC'" 1>/dev/null 19 | echo "Starting Client process..." 20 | gnome-terminal --title="Client" -e "bash -c 'java -cp ./jars/json-20180813.jar:./jars/mysql-connector-java-8.0.15.jar:./ Client'" 1>/dev/null 21 | echo "All processes running." 22 | -------------------------------------------------------------------------------- /students.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `students` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */; 2 | USE `students`; 3 | -- MySQL dump 10.13 Distrib 8.0.15, for Linux (x86_64) 4 | -- 5 | -- Host: localhost Database: students 6 | -- ------------------------------------------------------ 7 | -- Server version 8.0.15 8 | 9 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 10 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 11 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 12 | SET NAMES utf8 ; 13 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 14 | /*!40103 SET TIME_ZONE='+00:00' */; 15 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 16 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 17 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 18 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 19 | 20 | -- 21 | -- Table structure for table `studentinfo` 22 | -- 23 | 24 | DROP TABLE IF EXISTS `studentinfo`; 25 | /*!40101 SET @saved_cs_client = @@character_set_client */; 26 | SET character_set_client = utf8mb4 ; 27 | CREATE TABLE `studentinfo` ( 28 | `sid` int(11) NOT NULL, 29 | `name` varchar(60) DEFAULT NULL, 30 | `dob` varchar(15) DEFAULT NULL, 31 | `major` varchar(50) DEFAULT NULL, 32 | `level` varchar(15) DEFAULT NULL, 33 | `year` varchar(20) DEFAULT NULL, 34 | PRIMARY KEY (`sid`) 35 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 36 | /*!40101 SET character_set_client = @saved_cs_client */; 37 | 38 | -- 39 | -- Dumping data for table `studentinfo` 40 | -- 41 | 42 | LOCK TABLES `studentinfo` WRITE; 43 | /*!40000 ALTER TABLE `studentinfo` DISABLE KEYS */; 44 | INSERT INTO `studentinfo` VALUES (1,'Ayush Soni','5/19/97','Computer Science','Graduate','First'),(2,'Daniel Stafford','2/10/97','Information Security','Graduate','Second'),(3,'Mark Straten','5/12/96','Interactive Games and Media','Undergraduate','Senior'),(4,'Varun Sharma','8/22/99','Information Science and Technology','Undergraduate','Freshman'),(5,'Alexander Luoi','7/18/98','Cyber Security','Undergraduate','Sophomore'),(6,'Yu Kong','12/13/97','Computer Science','Graduate','First'),(7,'Peter Drinklage','8/27/98','Software Engineering','Undergraduate','Senior'); 45 | /*!40000 ALTER TABLE `studentinfo` ENABLE KEYS */; 46 | UNLOCK TABLES; 47 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 48 | 49 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 50 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 51 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 52 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 53 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 54 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 55 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 56 | 57 | -- Dump completed on 2019-02-23 19:38:03 58 | -------------------------------------------------------------------------------- /worker_list.txt: -------------------------------------------------------------------------------- 1 | localhost,20001 2 | localhost,20002 3 | localhost,20003 4 | localhost,20004 5 | localhost,20005 6 | --------------------------------------------------------------------------------