Click here to Skip to main content
15,885,546 members
Articles / Programming Languages / C#

WCF Router LoadBalancer

Rate me:
Please Sign up or sign in to vote.
4.92/5 (10 votes)
23 Jan 2014CPOL3 min read 39.5K   885   30   5
Illustrates how to make a primitive load balancer using the routing feature

Introduction

WCF routing is simple even if it is usually ranked as an advanced topic.

It depends greatly on your understanding of Filters, however implementing routing in .NET is really simple, let it be with configurations or within code.

Background

WCF routing is not known as a load balancer.

Load balancing can be complicated based on its type, it can be via software or hardware.

It can be either static or dynamic.

It can be based on location, requester, request or many other details or criteria.

This example is a trivial one, just to show that simple use of custom Message filter can balance requests on 10 different instances of the service sequentially.

It looks too simple that it can't be used outside tutoring.

But I am sure if you add more ideas and efforts to it, it can be a kernel to have a real load balancer.

Benefits of WCF Routing

  • Content-Based Routing
  • Protocol Bridging
  • Error Handling

How It Works

  1. Create your service (Jotter)
  2. Expose and endpoint ep1
  3. Host your service
  4. Create another service (routing)
  5. Expose another endpoint ep2
  6. Add routing behavior to the routing service.
  7. Host the routing service.
  8. Create a client that consumes service Jotter but through ep2.
  9. In order to be able to add the routing behavior, you need to add FilterTable in which you add Filters.

Types of Filters

  1. Action
  2. EndpointAddress
  3. EndpointAddressPrefix
  4. And
  5. Custom
  6. EndpointName
  7. MatchAll
  8. XPath

Using the Code

The code is in 2 parts.

The first project shows a simplest example of routing.

The second routerPlus is the load balancer.

This is the simplest code for routing:

C#
        static void Main(string[] args)
 {
 using (var serviceHost = new ServiceHost(typeof(Jotter)))
 { 

//Host the main service
 serviceHost.Open();
 //Host the routing service
 var routerEndpoint = new EndpointAddress("net.pipe://localhost/router");
 using (var routerHost = new ServiceHost(typeof(RoutingService)))
 {
 routerHost.Open();
 //Create the client that connects to the router endpoint
 var proxy = ChannelFactory<IJotter>.CreateChannel(new NetNamedPipeBinding(), routerEndpoint);
 while (!Console.KeyAvailable)
 Console.WriteLine(proxy.GetString());
 }
 }
 } 

The configurations are so important:

XML
<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior" >
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <routing filterTableName="myFilterTable"/>
        </behavior>
      </serviceBehaviors>
    
    </behaviors>
    <services>
      <service name="System.ServiceModel.Routing.RoutingService" 
      behaviorConfiguration="ServiceBehavior" >
        <endpoint address="net.pipe://localhost/router" 
        binding="netNamedPipeBinding" 
        contract="System.ServiceModel.Routing.IRequestReplyRouter"></endpoint>
      </service>

      <service name ="WcfRouting.Jotter" >
        <endpoint address="net.pipe://localhost/Jotter" 
        binding="netNamedPipeBinding" name="MainServiceEndPoint" 
        contract="WcfRouting.IJotter"></endpoint>
      </service>
    </services>
    <client>
      <endpoint name="serviceEndpoint" 
      address ="net.pipe://localhost/Jotter" contract="*" 
      binding="netNamedPipeBinding"></endpoint>
    </client>
    <routing>
      <filterTables>
        <filterTable name="myFilterTable">
          <add filterName="MatchAll" endpointName="serviceEndpoint"/>
        </filterTable>
      </filterTables>
      <filters>
        <filter name="MatchAll" filterType="MatchAll"/>
      </filters>
    </routing>
  </system.serviceModel>

The Load Balancer

As we saw above, there are various types of filters through which we are able to change the route of the requests by evaluating the name of the endpoint that a message was sent to, the SOAP action, or the address or address prefix that the message was sent to. Filters can also be joined with an AND condition, so that messages will only be routed to an endpoint if the message matches both filters. We can also create custom filters by creating our own implementation of MessageFilter.

Now having said that, and having known how to make a simple router, let's think of a way to extend this routing functionality to distribute requests equally on multiple endpoints.

For this purpose, we did the following:

  1. Create a service (Jotter)
  2. Host this service in 10 different hosts
  3. Expose 10 different endpoints one to each host (Using NetNamedPipeBinding)
  4. Create the routing service
  5. Expose the routing service through one endpoint. (Using NetTcpBinding)
  6. Add the routing behavior to the routing service and add our custom MessageFilter
  7. Create the Client, that consumes to the router's endpoint and the service Contract

Now for each request, the service will tell us what endpoint was used.

We will see that the load was evenly and equally distributed on all the hosts.

These hosts can be on different servers, where the router will send the requests to, however, beware of the binding you are using when testing this on different servers. NetNamedPipeBinding is not suitable then.

The load Balancer is shown here:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Routing;
using WcfRouting;

namespace WCFRoutingPlus
{
    class Program
    {
        private static int maxHostNumber = 10;

        static void Main(string[] args)
        {
            int index = 0;
            List<ServiceHost> lstServers = new List<ServiceHost>(maxHostNumber);
            for (int i = 0; i < maxHostNumber; i++)
            {
                lstServers.Add(new ServiceHost(typeof(Jotter)));
            }

            lstServers.ToList().ForEach(
            x =>
            {
                x.AddServiceEndpoint(typeof(IJotter), new NetNamedPipeBinding(), 
                string.Format("net.pipe://localHost/services/HostEndPoint{0}", ++index));
                x.Open();
            });


            try
            {
                var serverEndpoints = new List<ServiceEndpoint>();

                lstServers.ForEach(x =>
                {
                    serverEndpoints.AddRange(x.Description.Endpoints);
                }
                                    );
                Console.WriteLine("Hit <ENTER> to Move Next!!");
                Console.WriteLine("Hit <Any Other Key> to Terminate!!");
                using (var router = new ServiceHost(typeof(RoutingService)))
                {
                    string routerAddress = "net.tcp://localhost:8000/router/routerendpoint";
                    router.AddServiceEndpoint(typeof(IRequestReplyRouter), 
                    new NetTcpBinding(), routerAddress);
                    var config = new RoutingConfiguration();
                    LoadBalancerFilter.EndpointsNumber = maxHostNumber;
                    serverEndpoints.ForEach(x => 
                    config.FilterTable.Add(new LoadBalancerFilter(), new[] { x }, index--));
                    router.Description.Behaviors.Add(new RoutingBehavior(config));

                    //var debugBehavior = router.Description.Behaviors.Find<ServiceDebugBehavior>();
                    //debugBehavior.IncludeExceptionDetailInFaults = true;
                    router.Open();

                    var client = ChannelFactory<IJotter>.CreateChannel
                    (new NetTcpBinding(), new EndpointAddress(routerAddress));
                    while (Console.ReadKey().Key == ConsoleKey.Enter)
                    {
                        Console.WriteLine(client.GetString());
                    }

                    router.Close();
                }

                lstServers.ForEach(x => x.Close());
            }
            catch (Exception)
            {
                lstServers.ForEach(x => x.Abort());
            }
        }
    }
}

The Custom MessageFilter looks like:

C#
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;

namespace WCFRoutingPlus
{
    class LoadBalancerFilter:MessageFilter 
    {
        private static int _numberOfRejections = 0;
        private static int _Index = 0;
        private static int _numberOfEndpoints;


        public static int EndpointsNumber {
            set { _numberOfEndpoints = value; }
        }
        public override bool Match(Message mesage)
        {
            bool result = true;
            if (_numberOfRejections >= _Index)
            {

                result = true;
                _Index++;
                _numberOfRejections = 0;
            }
            else
            {
                result = false;
                _numberOfRejections++;
            }

            if (_Index >= _numberOfEndpoints)
                _Index = 0;

            return result;
        }
        public override bool Match(MessageBuffer buffer)
        {
            var message = buffer.CreateMessage();
            return true;
        }
    }
}

Result

 Image 1

To Do

  • Download the example, make sure you understand what is meant by this loadbalancing.
  • Come with new ideas and post them as comments and let's work on a good project.

History

  • 17th January, 2014: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
I graduated as an electronic engineer at 2000, and I have been working in software development ever since.
Interested mainly in .NET technologies.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Alex Erygin15-Apr-15 10:48
Alex Erygin15-Apr-15 10:48 
GeneralRe: My vote of 5 Pin
Assil17-Apr-15 4:42
professionalAssil17-Apr-15 4:42 
GeneralRe: My vote of 5 Pin
Alex Erygin27-Apr-15 21:56
Alex Erygin27-Apr-15 21:56 
Question[My vote of 2] Barely any infomation Pin
jfriedman17-Jan-14 14:39
jfriedman17-Jan-14 14:39 
GeneralMy vote of 2 Pin
Swab.Jat17-Jan-14 13:33
Swab.Jat17-Jan-14 13:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.