Sunteți pe pagina 1din 16

1! of !

16
Project 4: Load Balancing for Web Servers Using PyDirector
Cpre: 550
Instructor: Yong Guan
Ziji Guo
Introduction:
The objective of this machine problem is to build multiple load balancing across multiple
servers. In this project, I modified an existing load-balancing application to work in a distributed
way. This means that incoming requests for a website will be addressed to two or more loadbalancers (PyDirector), which forward requests and responses to the web servers themselves.
Design:
I have implemented code into two main class, and build a extra XML-RPC server by
python. The load driver send requests to the load balancer, and load balancer schedule the
requests to different IP address. In this case, I have maximum 6 host IP addresses, and 3 Load
Balancer. I will run my code in different situation to see if there is any influence between the
number of IP addresses and Load Balancer.

DriveLoa.py:
#Load Driver MainFile: driveload.py
#Written by Brendan Campbell in April 2008
import LoadDriver
Page 1! of 16
!

2! of !16
import sys
import xmlrpclib
from threading import Timer
from xml.dom.minidom import parse
#for arg in sys.argv:
#
print arg
filename = "config.xml"
if len(sys.argv)>=2:
if sys.argv[1].lower() == "help":
print "Usage: driveload [configfile] \nIf no configuration file is specified,
driveload will look for config.xml in the current directory"
sys.exit(0)
filename = sys.argv[1]
try:
xmlconfig = parse(filename)
hosts = []
for host in xmlconfig.getElementsByTagName('host'):
hosts.append([host.getElementsByTagName('hostname')
[0].childNodes[0].data,host.getElementsByTagName('port')[0].childNodes[0].data])
rate = float(xmlconfig.getElementsByTagName('rate')[0].childNodes[0].data)
period = float(xmlconfig.getElementsByTagName('period')[0].childNodes[0].data)
timeout = float(xmlconfig.getElementsByTagName('timeout')[0].childNodes[0].data)
except IndexError:
print "config.xml is not properly defined.\nThere must be: <rate>,<period>,<timeout>
elements.\nAlso, a <host> must have a <hostname> and a <port> element"
raise
except IOError:
print "Error while configuring. Is %s defined\n?"%filename,sys.exc_info()[0]
raise
print "Configured Behavior:"
print "-----------------"
print "rate:",rate
print "period:",period
print "timeout:",timeout
print "-----------------"
print "-----------------"
print "Configured Hosts:"
for host in hosts:
Page 2! of 16
!

3! of !16
print "-----------------"
print "url:",host[0]
print "port:",host[1]
#create timer
time_thread = Timer(period+timeout,LoadDriver.CollectResults)
terminator_thread = Timer(period,LoadDriver._terminator)
time_thread.start()
terminator_thread.start()
#launch host-testers
for host in hosts:
tr = LoadDriver.Spawner(host[0],host[1],rate,period,timeout)
tr.start()
#wait for the results to come in
time_thread.join(period+timeout+1)
#check if the result-gathering is successful
if time_thread.isAlive():
print "Error in timer thread!"
sys.exit(1)
#print the response times (in seconds) and the number of attempts
temporaryresult = LoadDriver.CollectionResults
print temporaryresult
sp = xmlrpclib.ServerProxy('http://0.0.0.0:8002')
loadbalancerlist = sp.getloadlist()
loadtime=sp.getloadtimelist()
loadlength = len(loadbalancerlist)-1
timeresult = temporaryresult[0]
time4first=0
time4second=0
secondposition = int(loadtime.index(loadbalancerlist[2]))
l=1
print "initialization finished"
print "length of timeresult:",len(timeresult)
#print "l:",l
print "Number of LoadBalancer:",loadlength
matchers = "LoadBalancer"
matching=[]
il=0
Page 3! of 16
!

4! of !16
for il in range(len(loadtime)):
if str(loadtime[il]).find(matchers)!=-1:
print loadtime[il]
matching.append(il)
print matching
#print "second position:",secondposition
#print "load time list:",loadtime
# while l <len(timeresult):
#
for lengt in range(len(matching)):
#
for i in range(len(loadtime)):
#
#print "l:",l
#
#print "loadtime:",loadtime[i]
#
if l == loadtime[i]:
#
if i<secondposition:
#
print "l:",l,"first load time:",time4first,"responde
time:",float(timeresult[l])
#
time4first = time4first+ float(timeresult[l])
#
l +=1
#
elif i>secondposition:
#
print "l:",l,"second load time:",time4first,"responde
time:",float(timeresult[l])
#
time4second = time4second + float(timeresult[l])
#
l+=1
#
l+=1
#
#else :

lengt=0
#print "length of the matching lsit:",len(matching)
for lengt in range(len(matching)):
if lengt == (len(matching)-1):
l=0
count =0
time4second=0
#print "into the second load"
temloadlist=loadtime[matching[lengt]:]
#print "second load time list:",temloadlist
while l <len(timeresult):
for i in range(len(temloadlist)):
if temloadlist[i]==l:
#print "l:",l,"second load time:",time4first,"responde
time:",float(timeresult[l])
Page 4! of 16
!

5! of !16
time4second = time4second+ float(timeresult[i])
l +=1
count +=1
l+=1
print temloadlist[0]+" total time is:",time4second," rate is:", float(time4second/
float(count))
else:
temloadlist=loadtime[matching[lengt]:matching[lengt+1]]
l=0
count=0
time4first=0
#print "first load time list:",temloadlist
while l <len(timeresult):
for i in range(len(temloadlist)):
if temloadlist[i]==l:
#print "l:",l,"first load time:",time4first,"responde
time:",float(timeresult[l])
time4first = time4first+ float(timeresult[i])
l +=1
count+=1
l+=1
print temloadlist[0]+" total time is:",time4first," rate is:", float(time4first/
float(count))
lengt+=1

pdschedule.py:
#
# Copyright (c) 2002-2004 ekit.com Inc (http://www.ekit-inc.com)
# and Anthony Baxter <anthony@interlink.com.au>
#
# $Id: pdschedulers.py,v 1.16 2004/12/14 13:31:39 anthonybaxter Exp $
#
import sys, time, xmlrpclib
if sys.version_info < (2,2):
class object: pass
import pdconf, pdlogging
loadname = "LoadBalancer"
def createScheduler(groupConfig):
schedulerName = groupConfig.scheduler
if schedulerName == "random":
Page 5! of 16
!

6! of !16
return RandomScheduler(groupConfig)
elif schedulerName == "leastconns":
return LeastConnsScheduler(groupConfig)
elif schedulerName == "roundrobin":
return RoundRobinScheduler(groupConfig)
elif schedulerName == "leastconnsrr":
return LeastConnsRRScheduler(groupConfig)
elif schedulerName == "roundrobin_edit":
return RoundRobinScheduler_edit(groupConfig)
else:
raise ValueError, "Unknown scheduler type `%s'"%schedulerName
class BaseScheduler:
schedulerName = "base"
def __init__(self, groupConfig):
self.hosts = []
self.hostnames = {}
self.badhosts = {}
self.open = {}
self.openconns = {}
self.totalconns = {}
self.lastclose = {}
self.loadConfig(groupConfig)
def loadConfig(self, groupConfig):
self.group = groupConfig
hosts = self.group.getHosts()
for host in hosts:
self.newHost(host.ip, host.name)
#print self.hosts
def getStats(self, verbose=0):
out = {}
out['open'] = {}
out['totals'] = {}
hc = self.openconns.items()
hc.sort()
for h,c in hc:
out['open']['%s:%s'%h] = c
hc = self.totalconns.items()
hc.sort()
Page 6! of 16
!

7! of !16
for h,c in hc:
out['totals']['%s:%s'%h] = c
bh = self.badhosts
out['bad'] = bh
return out
def showStats(self, verbose=1):
out = []
out.append( "%d open connections"%len(self.open.keys()) )
hc = self.openconns.items()
hc.sort()
out = out + [str(x) for x in hc]
if verbose:
oh = [x[1] for x in self.open.values()]
oh.sort()
out = out + [str(x) for x in oh]
return "\n".join(out)
def getHost(self, s_id, client_addr=None):
from time import time
host = self.nextHost(client_addr)
if host:
cur = self.openconns.get(host)
self.open[s_id] = (time(),host)
self.openconns[host] = cur+1
return host
else:
return None
def getHostNames(self):
return self.hostnames
def doneHost(self, s_id):
try:
t,host = self.open[s_id]
except KeyError:
#print "Couldn't find %s in %s"%(repr(s_id), repr(self.open.keys()))
return
del self.open[s_id]
cur = self.openconns.get(host)
if cur is not None:
self.openconns[host] = cur - 1
self.totalconns[host] += 1
Page 7! of 16
!

8! of !16
self.lastclose[host] = time.time()
def newHost(self, ip, name):
if type(ip) is not type(()):
ip = pdconf.splitHostPort(ip)
self.hosts.append(ip)
self.hostnames[ip] = name
self.hostnames['%s:%d'%ip] = name
self.openconns[ip] = 0
self.totalconns[ip] = 0
def delHost(self, ip=None, name=None, activegroup=0):
"remove a host"
if ip is not None:
if type(ip) is not type(()):
ip = pdconf.splitHostPort(ip)
elif name is not None:
for ip in self.hostnames.keys():
if self.hostnames[ip] == name:
break
raise ValueError, "No host named %s"%(name)
else:
raise ValueError, "Neither ip nor name supplied"
if activegroup and len(self.hosts) == 1:
return 0
if ip in self.hosts:
self.hosts.remove(ip)
del self.hostnames[ip]
del self.openconns[ip]
del self.totalconns[ip]
elif self.badhosts.has_key(ip):
del self.badhosts[ip]
else:
raise ValueError, "Couldn't find host"
return 1
def deadHost(self, s_id, reason=''):
from time import time
t,host = self.open[s_id]
if host in self.hosts:
pdlogging.log("marking host %s down (%s)\n"%(str(host), reason),
datestamp=1)
self.hosts.remove(host)
Page 8! of 16
!

9! of !16
if self.openconns.has_key(host):
del self.openconns[host]
if self.totalconns.has_key(host):
del self.totalconns[host]
self.badhosts[host] = (time(), reason)
# make sure we also mark this session as done.
self.doneHost(s_id)
def nextHost(self):
raise NotImplementedError
class RandomScheduler(BaseScheduler):
schedulerName = "random"
def nextHost(self, client_addr):
import random
if self.hosts:
pick = random.choice(self.hosts)
return pick
else:
return None
class RoundRobinScheduler(BaseScheduler):
schedulerName = "roundrobin"
counter = 0
def nextHost(self, client_addr):
if not self.hosts:
return None
if self.counter >= len(self.hosts):
self.counter = 0
if self.hosts:
d = self.hosts[self.counter]
self.counter += 1
return d
class RoundRobinScheduler_edit(BaseScheduler):
import xmlrpclib
schedulerName = "roundrobin_edit"
counter = 0
def nextHost(self, client_addr):
Page 9! of 16
!

! of !16
10
lenthofhost = len(self.hosts)
namecounter=0
global loadname
if not self.hosts:
return None
if self.hosts:
import xmlrpclib
sp = xmlrpclib.ServerProxy('http://0.0.0.0:8002')
print "-------*--------"
if loadname=="LoadBalancer":
print "first time ask name"
loadname= sp.askname(loadname)
sp.initialloadlist(loadname)
temp = sp.addfuntion()
print "loadname:",loadname
print "load list:",sp.getloadlist()
print "count :",temp
print "length of host:",lenthofhost
print sp.addtime(temp,loadname)
self.counter = temp
if self.counter>=lenthofhost:
self.counter = temp%lenthofhost
print "self.counter:",self.counter
d = self.hosts[self.counter]
print "d:",d
return d
class LeastConnsScheduler(BaseScheduler):
"""
This scheduler passes the connection to the destination with the
least number of current open connections. This is a very cheap
and quite accurate method of load balancing.
"""
schedulerName = "leastconns"
counter = 0
def nextHost(self, client_addr):
if not self.openconns.keys():
return None
hosts = [ (x[1],x[0]) for x in self.openconns.items() ]
hosts.sort()
return hosts[0][1]
Page 10
! of 16
!

! of !16
11

class LeastConnsRRScheduler(BaseScheduler):
"""
The basic LeastConnsScheduler has a problem - it sorts by
open connections, then by hostname. So hostnames that are
earlier in the alphabet get many many more hits. This is
suboptimal.
"""
schedulerName = "leastconnsrr"
counter = 0
def nextHost(self, client_addr):
if not self.openconns.keys():
return None
hosts = [ (x[1], self.lastclose.get(x[0],0), x[0])
for x in self.openconns.items() ]
hosts.sort()
return hosts[0][2]
xmlrpcserver.py
from SimpleXMLRPCServer import SimpleXMLRPCServer
server = SimpleXMLRPCServer(('0.0.0.0',8002))
feed = 0
loadlist =[]
loadbalancer=["LoadBalancer"]

def addfuntion():
global feed
feed = feed + 1
print feed
return feed
def addtime(count,name):
print "adding the count of time----------------+++++++++"
global loadbalancer
flag=findlocate(name)
print "flag is :",flag
loadlist.insert(flag+1,count)
#loadlist = loadlist[:i]+count+loadlist[i:]
print loadlist
return loadlist
def initialloadlist(name):
Page !11 of !16

! of !16
12
global loadlist
print "--------------initial the load list----------------"
loadlist.append(name)
print loadlist
def askname(name):
global loadbalancer
print name
print "askname in server side"
if loadbalancer[0] is not None:
print "first element is not none"
while findload(name):
name = name + "I"
if findload(name)!=True:
loadbalancer.append(name)
return name
loadbalancer.append(name)
return name
def findload(name):
global loadbalancer
print "finding name:",name
print "name list:",loadbalancer
for s in loadbalancer:
if s==name:
return True
return False
def findlocate(name):
print "find the location of ",name
global loadlist
print "length of loadlist is:",len(loadlist)
for i in range(len(loadlist)):
print "i :",i
if name == loadlist[i]:
print "location is :",i
return i
print "cannot find location"
print "list is :", loadlist
return None

def getloadlist():
return loadbalancer
Page 12
! of 16
!

! of !16
13
def getloadtimelist():
return loadlist
server.register_function(getloadlist)
server.register_function(getloadtimelist)
server.register_function(addfuntion)
server.register_function(addtime)
server.register_function(initialloadlist)
server.register_function(askname)
server.register_function(getloadlist)
server.serve_forever()
Sample of LoadBalancer.xml
<pdconfig>
<service name="loadbalancer-2" >
<listen ip="0.0.0.0:10026" />
<group name="servers" scheduler="roundrobin_edit">
<!--<host name="www.iastate.edu" ip="129.186.23.166" />
<host name="www.google.com" ip="174.125.196.99" />
<host name="www.douyutv.com" ip="8.37.234.3" />
<host name="www.zhanqi.tv" ip="119.188.96.120" />
<host name="www.baidu.com" ip="180.76.3.151" />
-->
<host name="www.cnn.com[1]" ip="157.166.255.19:80" />
<host name="www.cnn.com[2]" ip="157.166.255.18:80" />
<host name="www.cnn.com[3]" ip="157.166.226.26:80" />
<host name="www.cnn.com[4]" ip="157.166.226.25:80" />
<host name="www.cnn.com[5]" ip="157.166.224.26:80" />
<host name="www.cnn.com[6]" ip="157.166.224.25:80" />
</group>
<enable group="servers" />
</service>
<logging file="pydir.log"/>
</pdconfig>
Sample of Config.xml for LoadDriver
<!--config.xml for Load Driver -->
<rootconfig>
<rate>15</rate> <!--rate is defined as requests per second -->
<period>1</period> <!-- period is the number of seconds to send requests -->
<timeout>1</timeout> <!-- timeout is the number of seconds to wait for the last requests to
finish-->
<host>
Page 13
! of 16
!

! of !16
14
<hostname>0.0.0.0</hostname>
<port>10026</port>
</host>
<host>
<hostname>0.0.0.0</hostname>
<port>10028</port>
</host>
<host>
<hostname>0.0.0.0</hostname>
<port>10024</port>
</host>
</rootconfig>
Implementation:
I implemented the xml-RPC client code into the pdschedule.py file and I edited the RR
algorithm to make the load balancer send the data to my xml-rpc server. And my xml-rpc would
record all the data it got, schedule the data and return it to the load driver for output.
In my load driver python file, I build a algorithm to read the data from my server.

Testing:
1) Only one web server configured into place with two load-balancers. Drive load to saturation
and mark the rate.

Page 14
! of 16
!

! of !16
15
2) Two web servers configured with two load-balancers. Drive load to saturation again and mark
the rate.

3) Four web servers configured with two load-balancers. Drive to saturation and mark the rate.

Page 15
! of 16
!

! of !16
16
4) Full configuration, maximum load-balancers, maximum web servers. Drive a heterogeneous
load with one of the load balancers taking many more connections than the other(s).
Answer: I run the 3 LoadBalancer which are: LoadBalancerI, LoadBalancerII and
LoadBalancerIII, and LoadBalancerII has 6 host IP addresses, LoadBalancerI has 4 host IP
addresses and LoadBalancerIII has 2 host IP addresses. we can see the rate for all these three are
quite close, even they different number of the host IP addresses. The number of the addresses
only matter the total requests it can handled.

Conclusion
We could see at first three test, there are 2 requests lost out of 30, but the last test, we
send 45 requests in total and have 9 requests lost. So that we could say more requests we sent,
higher percentage of the lost package. From the above tests we could see more web servers
means slower rate, I think this is because the less web web servers the less communication need
to take between the LoadBalancer. And the number of web server for every single LoadBalancer
only influence the number of requests it sent. In the 11am today, I have about 30% package lost,
and later today, it decreased to 6%. So that, I think the percentage of the lost package depends on
the server for the web server.

Page 16
! of 16
!

S-ar putea să vă placă și