Scaling our application

At the beginning of this chapter, we talked about the need to scale applications. So far, we have shown how to take a very simple client-server application and split it between two instances of Python.

However, scaling applications typically implies running multiple copies of an application in order to handle larger loads. Usually, some type of load balancer is needed. Let's explore how we can use Spring Python's remoting services mixed with some simple Python to create a multi-node version of our service.

Converting the single-node backend into multiple instances

The first step is to create another application context. We are going to spin up a different configuration of components, that is a different blue print, and plan to re-use our existing business code without making any changes.

  1. Create a two-node configuration, which creates two instances of Service, advertised on different ports.
    from springpython.config import *
    from springpython.context import scope
    from springpython.remoting.pyro import *
    from simple_service import *
    class HappyBirthdayContext(PythonConfig):
    def __init__(self):
    PythonConfig.__init__(self)
    @Object(scope.PROTOTYPE)
    def target_service(self):
    return Service()
    @Object()
    def service_exporter1(self):
    exporter = PyroServiceExporter()
    exporter.service_name = "service"
    exporter.service = self.target_service()
    exporter.service_port = 7001
    return exporter
    @Object()
    def service_exporter2(self):
    exporter = PyroServiceExporter()
    exporter.service_name = "service"
    exporter.service = self.target_service()
    exporter.service_port = 7002
    return exporter
    
  2. In this context, we have two different PyroServiceExporters, each with a different port number. While it appears they are both using the target_service, notice how we have changed the scope to PROTOTYPE. This means each exporter gets its own instance. To run this new context, we need a different startup script.
    import logging
    from springpython.context import *
    from multi_server_ctx import *
    if __name__ == "__main__":
    logger = logging.getLogger("springpython.remoting")
    loggingLevel = logging.DEBUG
    logger.setLevel(loggingLevel)
    ch = logging.StreamHandler()
    ch.setLevel(loggingLevel)
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    ctx = ApplicationContext(HappyBirthdayContext())
    
  3. Now let's launch the script:
Converting the single-node backend into multiple instances

In this script, we turned on some of Spring Python's built in logging, so we could see some of parts involved with starting up remoting. We can see two different Pyro daemon threads being launched, one for each of the ports. And then we see two instances of our Service code being registered, one with each thread.

We now have two copies of our service ready and waiting to be called.

Creating a round-robin dispatcher

There are many different ways to code a dispatcher. For our example, let's pick a simple round robin algorithm where we cycle through a fixed list.

Let's create an application context that includes a round robin dispatcher that acts like our service, fed with a list of PyroProxyFactory elements.

from springpython.config import *
from springpython.remoting.pyro import *
class RoundRobinDispatcher(object):
def __init__(self, proxies):
self.proxies = proxies
self.counter = 0
def happy_birthday(self, parm):
self.counter += 1
proxy = self.proxies[self.counter % 2]
print "Calling %s" % proxy
return proxy.happy_birthday(parm)
class HappyBirthdayContext(PythonConfig):
def __init__(self):
PythonConfig.__init__(self)
@Object
def service_proxies(self):
proxies = []
for port in ["7001", "7002"]:
proxy = PyroProxyFactory()
proxy.service_url ="PYROLOC://127.0.0.1:%s/service" % port
proxies.append(proxy)
return proxies
@Object
def service(self):
return RoundRobinDispatcher(self.service_proxies())

We configured service_proxies as an array of PyroProxyFactorys. This lets us define a static list of connections.

The RoundRobinDispatcher class is injected with the proxies, and mimics the API of our Happy Birthday service. Every call into the dispatcher increments the counter. It then picks one of the proxies to make the actual call.

Adjusting client configuration without client code knowing its talking to multiple node backend

Let's write the client script to use our round robin dispatcher.

from springpython.context import *
from multi_client_ctx import *
if __name__ == "__main__":
ctx = ApplicationContext(HappyBirthdayContext())
s = ctx.get_object("service")
print "
".join(s.happy_birthday("Greg"))
print "
".join(s.happy_birthday("Greg"))
print "
".join(s.happy_birthday("Greg"))

There is hardly an impact to the client. The only difference is the import statement, and the fact that we are calling the service multiple times. If the client was actually a GUI application with this tied to a button, there would be no difference.

Now let's run the client script.

Adjusting client configuration without client code knowing its talking to multiple node backend

As can be seen, each invocation shows which ProyProxyFactory was called, and they clearly show switching back and forth between the two proxies registered. With each call smoothly integrated with a corresponding PyroServiceExporter, we have scaled our application into a two-node version.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.32.1