Advanced Usage¶
Advanced features and load balancer configuration options.
Configuring the LoadBalancer¶
The LoadBalancer
takes a number of configuration options in order to suit different environments.
At its most basic, it just requires a ServerList
implementation:
import ballast
from ballast.discovery.static import StaticServerList
servers = StaticServerList(['127.0.0.1', '127.0.0.2'])
load_balancer = ballast.LoadBalancer(servers)
Now, you can configure a Service
with the load balancer:
my_service = ballast.Service(load_balancer)
Make an HTTP request as you would with requests - using a relative path instead of an absolute URL:
response = my_lb_service.get('/v1/path/to/resource')
# <Response[200]>
The following options are enabled by default when no other options are specified:
from ballast import rule, ping
ballast.LoadBalancer(
servers, # required
rule=rule.RoundRobinRule(),
ping_strategy=ping.SerialPingStrategy(),
ping=ping.SocketPing()
)
Dynamic Server Discovery¶
Servers can be discovered dynamically by configuring one of the dynamic ServerList
implementations (or creating your own) on the LoadBalancer
. The ServerList
is periodically queried by the LoadBalancer
for updated Server
objects.
DNS¶
NOTE: Using DNS features requires additional dependencies. From the command line, install the DNS dependencies from pip:
$ pip install ballast[dns]
To use DNS to query Server
instances, configure a LoadBalancer
with either a
DnsARecordList
to query A records
import ballast
from ballast.discovery.ns import DnsARecordList
servers = DnsARecordList('my.service.internal.')
load_balancer = ballast.LoadBalancer(servers)
Or use DnsServiceRecordList
to query SRV records
import ballast
from ballast.discovery.ns import DnsServiceRecordList
servers = DnsServiceRecordList('my.service.internal.')
load_balancer = ballast.LoadBalancer(servers)
Consul REST API¶
To use Consul (via HTTP REST API) to query Server
instances, configure a LoadBalancer
with ConsulRestRecordList
import ballast
from ballast.discovery.consul import ConsulRestRecordList
servers = ConsulRestRecordList('http://my.consul.url:8500', 'my-service')
load_balancer = ballast.LoadBalancer(servers)
Load-Balancing Rules¶
The logic of how to choose the next server in the load-balancing pool is configurable by specifying a
Rule
implementation.
RoundRobinRule¶
The RoundRobinRule
chooses each server in the load-balancing pool an equal number of times by
simply looping through the collection of servers in the pool:
import ballast
from ballast import rule
servers = ... # defined earlier
my_rule = rule.RoundRobinRule()
load_balancer = ballast.LoadBalancer(servers, my_rule)
PriorityWeightedRule¶
The PriorityWeightedRule
chooses each server in the load-balancing pool based on a combination of
priority and weight.
Given a pool of 5 servers with the following priority/weight values, this rule will choose priority 1 servers exclusively (unless/until all priority 1 servers are down, in which case it will move on to priority 2 servers):
# priority 1
Server(address='127.0.0.1', priority=1, weight=60)
Server(address='127.0.0.2', priority=1, weight=20)
Server(address='127.0.0.3', priority=1, weight=20)
# priority 2 (backups)
Server(address='127.0.0.4', priority=2, weight=1)
Server(address='127.0.0.5', priority=2, weight=1)
Of the current priority 1 servers, the choice of server will be determined by its weight as a ratio. 60% of the traffic will go to 127.0.0.1 while the remaining 40% will be split evently between 127.0.0.2 and 127.0.0.3 (both have the same weight):
Server(address='127.0.0.1', priority=1, weight=60)
If all priority 1 servers are down, this rule will split traffic between 127.0.0.4 and 127.0.0.5 equally (both have the same weight).
For this rule to work correctly, it must be paired with a ServerList
that provides priority and weight as part of its discovery (e.g. DnsServiceRecordList
):
import ballast
from ballast import rule
from ballast.discovery.ns import DnsServiceRecordList
# use a ServerList that provides 'priority' and 'weight'
servers = DnsServiceRecordList('my.service.internal.')
my_rule = rule.PriorityWeightedRule()
load_balancer = ballast.LoadBalancer(servers, my_rule)
Pinging Servers¶
The LoadBalancer
periodically queries for servers as well as attempts to ping each server to ensure
it’s up, running and responding. This can be configured via the following standard Ping
implementations (or you can create your own):
DummyPing¶
DummyPing
doesn’t actually ping any servers, it just assumes the server is active - useful for
testing or when otherwise not wanting to actually ping servers in the load balancing pool. Not recommended for production.
SocketPing¶
SocketPing
attempts to open a socket connection to the server. If the connection was successful,
the ping is considered successful.
UrlPing¶
UrlPing
attempts to make a GET request to the server. If the request returns a 2xx status
code, the ping is considered successful.
Ping Strategies¶
The LoadBalancer
initiates its periodic ping using a configurable PingStrategy
.
The following strategies are available (or you can create your own):
SerialPingStrategy¶
The SerialPingStrategy
iterates through each Server
attempting to ping
each one sequentially. The time it takes for this strategy to complete is ping time x number of servers.
It is recommended to use this strategy only when there are a (known) small number of servers.
ThreadPoolPingStrategy¶
The ThreadPoolPingStrategy
iterates through each Server
attempting to ping
each server in parallel using a ThreadPool
. The time it takes for this strategy to complete
is not much longer than the time it takes for a single ping to complete.
NOTE: this class does not play well when using gevent. It’s recommended to use the
GeventPingStrategy
instead for gevent-based systems.
MultiprocessingPoolPingStrategy¶
The MultiprocessingPoolPingStrategy
iterates through each Server
attempting to ping
each server in parallel using a Pool
. The time it takes for this strategy to complete
is not much longer than the time it takes for a single ping to complete, however, on systems where a large number of servers
are queried, it’s recommended to use ThreadPoolPingStrategy
instead.
NOTE: this class does not play well when using gevent. It’s recommended to use the
GeventPingStrategy
instead for gevent-based systems.