The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

ResourcePool::LoadBalancer - A LoadBalancer across ResourcePools

SYNOPSIS

 use ResourcePool::LoadBalancer;

 my $loadbalancer = ResourcePool::LoadBalancer->new($key, @options);


 $loadbalancer->add_pool($some_ResourcePool);
 $loadbalancer->add_pool($some_other_ResourcePool);
 $loadbalancer->add_pool($third_ResourcePool);

 
 my $resource = $loadbalancer->get();  # get a resource from one pool
                                       # according to the policy
 [...]                                 # do something with $resource
 $loadbalancer->free($resource);       # give it back to the pool

 $loadbalancer->fail($resource);       # give back a failed resource

DESCRIPTION

The LoadBalancer is a generic way to spread requests to different ResourcePools to increase performance and/or availibility.

Beside the construction the interface of a LoadBalancer is the same as the interface of a ResourcePool. This makes it very simple to change a program which uses ResourcePool to use the LoadBalancer by just changing the construction (which is hopefully kept at a central point in your program).

LoadBalancer->new($key, [@Options])

Creates a new LoadBalancer. This method takes one key to identify the LoadBalancer (used by ResourcePool::Singleton). It is recommended to use some meaningful string as key, since this is used when errors are reported. This key is internally used to distingush between different pool types, e.g. if you have two LoadBalancer one for DBI connections to different servers and use parallel another LoadBalancer for Net::LDAP connections.

Options

Policy

With this option you can specify which ResourcePool is used if you ask the LoadBalancer for a resource. LeastUsage takes always the ResourcePool with the least used resources. This is usually a vital inidcator for the machine with the lowest load. RoundRobin iterates over all available ResourcePools regardless of the usage of the ResourcePools. FallBack uses always the first ResourcePool if it works, only if there is a problem it takes the second one (and so on).

In every case the LoadBalancer tries to find a valid resource for you, this option only affects the order in which the ResourcePools are checked.

Default: LeastUsage

MaxTry

The MaxTry option specifies how often the LoadBalancer checkes all it's ResourcePools for a valid resource before it gives up and returns undef on the get() call. This option is similar to the same named option from the ResourcePool.

Default: 6

SleepOnFail

Very similar to the same named option from ResourcePool. Tells the LoadBalancer to sleep if it was not able to find a valid resource in ANY of the underlying ResourcePools. So, in the worst case, get() tries all ResourcePools (all which are not suspended) to obtain a valid resource, if this fails it sleeps. After this sleep all pools are checked again and if it was still not possible to get a vaild resource it sleeps again. This is done up to MaxTry times before get() returns undef.

With this option you can specify the time in seconds which will be slept if it was not possible to obtain a valid resource from any of the ResourcePools.

Default: [0,1,2,4,8]

$loadbalancer->add_pool($resourcepool, @options)

Adds a ResourcePool object to the LoadBalancer. You must call this method multiple times to add more pools.

There are two options which affect the way the LoadBalancer selects the ResourcePools. Weight may make one ResourcePool more relevant then others. A ResourcePool with a high Weight is more expansive then a ResourcePool with a low Weight and will be used less frequent by the LoadBalancer. SuspendTimeout specifies the timeout in seconds if this ResourcePool returns a undef, see below.

You can add as many ResourcePools as you want.

Defaults: Weight = 100, SuspendTimeout = 5

$loadbalancer->get

Returns a resource. This resource has to be given back via the free() or fail() method. The get() method calls the get() method of the ResourcePool and might therefore return undef if no valid resource could be found in the ResourcePool. In that case the LoadBalancer tries up to MaxTry times to get a valid resource out of the ResourcePools. If the LoadBalancer finds a invalid ResourcePool (a ResourcePool which returns a undef) it suspends this ResourcePool and re-applies the Policy to get a valid resource. The SuspendTimeout is configureable on a per ResourcePool basis.

According to the MaxTry and SleepOnFail settings the get() call might block the programs execution if it is not able to find a valid resource right now. Provided that you use the default settings for MaxTry and SleepOnFail a call to get() will block at least for 15 seconds before returning undef. Please see "TIMEOUTS" in ResourcePool for some other effects which might affect the time which get() blocks before it returns undef.

$loadbalancer->free($resource)

Marks a resource as free. Basically the same as the free() method of the ResourcePool. Return value is 1 on success or 0 if the resource doesn't belong to one of the underlieing pools.

$loadbalancer->fail($resource)

Maks the resource as bad. Basically the same as the fail() method of the ResourcePool. Return value is 1 on success or 0 if the resource doesn't belong to one of the underlieing pools.

EXAMPLE

A basic example...

 use ResourcePool;
 use ResourcePool::Factory::Net::LDAP;
 use ResourcePool::LoadBalancer;

 ### LoadBalancer setup 

 # create a pool to a ldap server
 my $factory1 = ResourcePool::Factory::Net::LDAP->new("ldap.you.com");
 my $pool1    = ResourcePool->new($factory1);

 # create a pool to another ldap server
 my $factory2 = ResourcePool::Factory::Net::LDAP->new("ldap2.you.com");
 my $pool2    = ResourcePool->new($factory2);

 # create a empty loadbalancer with a FallBack policy
 my $loadbalancer = ResourcePool::LoadBalancer->new("LDAP", 
                        Policy => "FallBack");

 # add the first pool to the LoadBalancer
 # since this LoadBalancer was configured to use the FallBack
 # policy, this is the primary used pool
 $loadbalancer->add_pool($pool1);

 # add the second pool to the LoadBalancer.
 # This pool is only used when first pool failes
 $loadbalancer->add_pool($pool2);

 ### LoadBalancer usage (no difference to ResourcePool)
 
 for (my $i = 0; $i < 1000000 ; $i++) {
    my $resource = $loadbalancer->get();  # get a resource from one pool
                                          # according to the policy
        if (defined $resource) {
       eval {
          #[...]                          # do something with $resource
          $loadbalancer->free($resource); # give it back to the pool
       }; 
       if ($@) { # an exception happend
          $loadbalancer->fail($resource); # give back a failed resource
       }
        } else {
           die "The LoadBalancer was not able to obtain a valid resource\n";
    }
 }

Please notice that the get()/free() stuff in this example in INSIDE the loop. This is very important to make sure the loadbalacing and failover works. As for the ResourcePool the smartness of the LoadBalancer lies in the get() method, so if you do not call the get() method regulary you can not expect the LoadBalancer to handle loadbalancing or failover.

The example above does not do any loadbalancing since the policy was set to FallBack. If you would change this to RoundRobin or LeastUsage you would spread the load across both servers.

You can directly copy and past the example above and try to run it. If you do not change the hostnames you will just see how it fails, but even this will tell you a lot about how it works. Give it a try.

Now lets make a slightly more complex configuration. Imagine you have three ldap servers: one master where you do your write access, two replicas where you do the read access. Now we want the ResourcePool to implement a loadbalacing across the two replicas but we want it to use the master also for read access if both replicas are not available.

This setup is simple if you keep in mind what I have said about the LoadBalancer interface: "Beside the construction the interface of a LoadBalancer is the same as the interface of a ResourcePool". This means that it is possible to make a nested LoadBalacer chain.

 use ResourcePool;
 use ResourcePool::Factory::Net::LDAP;
 use ResourcePool::LoadBalancer;

 ### LoadBalancer setup 

 # create a pool to the master
 my $masterfactory   = ResourcePool::Factory::Net::LDAP->new("master");
 my $master          = ResourcePool->new($masterfactory);

 # create pools to the replicas
 my $replica1factory = ResourcePool::Factory::Net::LDAP->new("replica1");
 my $replica1        = ResourcePool->new($replica1factory);

 my $replica2factory = ResourcePool::Factory::Net::LDAP->new("replica2");
 my $replica2        = ResourcePool->new($replica2factory);

 # create the loadbalacer to spread load across the two replicas
 # using the default Policy LeastUsage
 my $replicaLB       = ResourcePool::LoadBalancer->new("LDAP-Replica");
 $replicaLB->add_pool($replica1);
 $replicaLB->add_pool($replica2);

 # create a suprior loadbalancer which handels the fallback to the
 # master if both replicas fail.
 my $loadbalancer = ResourcePool::LoadBalancer->new("LDAP", 
                        Policy => "FallBack");
 $loadbalancer->add_pool($replicaLB);   # HERE IS THE MAGIC
 $loadbalancer->add_pool($master);

 ### LoadBalancer usage is the same as above, therfore skipped here 

You should keep in mind that this configuration causes a multiplication of the timeout's which are done because of the SleepOnFail settings. In the example above the sleeps sum up to 60 seconds.

SEEL ALSO

ResourcePool(3pm)

AUTHOR

    Copyright (C) 2002 by Markus Winand <mws@fatalmind.com>

    This program is free software; you can redistribute it and/or
    modify it under the same terms as Perl itself.