Introduction
The example demonstrates a communication scenario where the capacity of one running service is not enough and you want to increase the performance by distributing the workload across multiple services.
To simulate the scenario, the example below implements a client application using a service to calculate π (PI) number. The calculation is split into many small intervals (400 in our example).
Therefore, the service gets overloaded and the performance drops down.
The solution for such scenarios could be using of load balancer. The load balancer is a component maintaining a farm of services (typically same services but running on different machines) and is exposed to receive messages instead of the real service. The client does not know about this setup and thinks it communicates directly with the service. When the client sends a request, the load balancer receives this message and forwards it to the service picked from the farm. The service processes the message and sends back the response that is routed via the load balancer back to the client.
To run the example bellow you must execute applications in the following order:
1. PiCalculator up to 3 instances.
2. LoadBalancer
3. Client
The example bellow uses Eneter Messaging Framework
that provides functionality for various communication scenarios.
(The framework is free and can be downloaded from http://www.eneter.net.
More detailed technical info can be found at http://www.eneter.net/OnlineHelp/EneterMessagingFramework/Index.html.)
Client
The client is a simple application containing a button sending the request to the service to calculate π (PI) number. The calculation is split into many small intervals. Therefore, the client sends about 400 messages each containing a small interval for the calculation.
Then it collects all responses and summarizes them together to get the final result, the PI number.
using System;
using System.Windows.Forms;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
namespace Client
{
public partial class Form1 : Form
{
public class Range
{
public double From;
public double To;
}
public Form1()
{
InitializeComponent();
OpenConnection();
}
public void OpenConnection()
{
IMessagingSystemFactory myMessaging
= new TcpMessagingSystemFactory();
IDuplexOutputChannel anOutputChannel
= myMessaging.CreateDuplexOutputChannel("tcp://127.0.0.1:8060/");
IDuplexTypedMessagesFactory aSenderFactory
= new DuplexTypedMessagesFactory();
mySender = aSenderFactory.CreateDuplexTypedMessageSender<double, Range>();
mySender.ResponseReceived += OnResponseReceived;
mySender.AttachDuplexOutputChannel(anOutputChannel);
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
mySender.DetachDuplexOutputChannel();
}
private void CalculatePiBtn_Click(object sender, EventArgs e)
{
myCalculatedPi = 0.0;
for (double i = -1.0; i <= 1.0; i += 0.005)
{
Range anInterval = new Range() { From = i, To = i + 0.005 };
mySender.SendRequestMessage(anInterval);
}
}
private void OnResponseReceived(object sender, TypedResponseReceivedEventArgs<double> e)
{
myCalculatedPi += e.ResponseMessage;
InvokeInUIThread(() => ResultTextBox.Text = myCalculatedPi.ToString());
}
private void InvokeInUIThread(Action uiMethod)
{
if (InvokeRequired)
{
Invoke(uiMethod);
}
else
{
uiMethod();
}
}
private IDuplexTypedMessageSender<double, Range> mySender;
private double myCalculatedPi;
}
}
Load Balancer
The load balancer is a simple console application acting as a real service. In this example it has a farm of three services. (To keep things simple, all three services run on the same computer. They listens to different ports.)
When a user presses the button in the client application, the load balancer receives about 400 request messages and distributes them to its three services from the farm.
To decide which service is picked from the farm, the load balancer uses Round-Robin algorithm. Therefore, all services are chosen evenly.
(The Round-Robin algorithm is suitable for load balancing if processing of requests takes approximately same time.)
The code is very simple. The whole implementation is here:
using System;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.Messaging.Nodes.LoadBalancer;
namespace LoadBalancer
{
class Program
{
static void Main(string[] args)
{
IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
ILoadBalancerFactory aLoadBalancerFactory
= new RoundRobinBalancerFactory(aMessaging);
ILoadBalancer aLoadBalancer
= aLoadBalancerFactory.CreateLoadBalancer();
string[] anAvailableServices = {
"tcp://127.0.0.1:8071/", "tcp://127.0.0.1:8072/", "tcp://127.0.0.1:8073/" };
foreach (string anIpAddress in anAvailableServices)
{
aLoadBalancer.AddDuplexOutputChannel(anIpAddress);
}
IDuplexInputChannel anInputChannel
= aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:8060/");
aLoadBalancer.AttachDuplexInputChannel(anInputChannel);
Console.WriteLine("Load Balancer is running.\r\nPress ENTER to stop.");
Console.ReadLine();
aLoadBalancer.DetachDuplexInputChannel();
}
}
}
PI Calculator
The calculator is a simple service (console application). It receives requests to perform calculations for specific intervals. When the calculation is done, it sends back the calculation result.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
namespace PiCalculator
{
public class Range
{
public double From;
public double To;
}
class Program
{
static void Main(string[] args)
{
string aServiceAddress = GetMyAddress();
if (aServiceAddress == "")
{
Console.WriteLine("The service could not start because all possible ports are occupied.");
return;
}
IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
IDuplexInputChannel anInputChannel = aMessaging.CreateDuplexInputChannel(aServiceAddress);
IDuplexTypedMessagesFactory aReceiverFactory = new DuplexTypedMessagesFactory();
IDuplexTypedMessageReceiver<double, Range> aReceiver
= aReceiverFactory.CreateDuplexTypedMessageReceiver<double, Range>();
aReceiver.MessageReceived += OnMessageReceived;
aReceiver.AttachDuplexInputChannel(anInputChannel);
Console.WriteLine("Root Square Calculator listening to " + aServiceAddress +
" is running.\r\n Press ENTER to stop.");
Console.ReadLine();
aReceiver.DetachDuplexInputChannel();
}
private static void OnMessageReceived(object sender, TypedRequestReceivedEventArgs<Range> e)
{
Console.WriteLine("Calculate From: {0} To: {1}", e.RequestMessage.From, e.RequestMessage.To);
double aResult = 0.0;
double aDx = 0.000000001;
for (double x = e.RequestMessage.From; x < e.RequestMessage.To; x += aDx)
{
aResult += 2 * Math.Sqrt(1 - x * x) * aDx;
}
IDuplexTypedMessageReceiver<double, Range> aReceiver
= (IDuplexTypedMessageReceiver<double, Range>)sender;
aReceiver.SendResponseMessage(e.ResponseReceiverId, aResult);
}
private static string GetMyAddress()
{
List<int> aPossiblePorts = new List<int>(new int[]{ 8071, 8072, 8073 });
IPGlobalProperties anIpGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
IPEndPoint[] aTcpListeners = anIpGlobalProperties.GetActiveTcpListeners();
foreach (IPEndPoint aListener in aTcpListeners)
{
aPossiblePorts.Remove(aListener.Port);
}
if (aPossiblePorts.Count > 0)
{
return "tcp://127.0.0.1:" + aPossiblePorts[0] + "/";
}
return "";
}
}
}
And here are all applications communicating together.
My programming path started in 1987 when I got my first computer Sharp MZ-800.
It came with 8 bit CPU Z80, 64Kb RAM and the tape recorder. It was a great machine. I think I still have it somewhere.
I was fascinated and I started to write first programs. Eventually I became developer and software architect. I like innovations and clean nice solutions.