Scaling microservices with Crossbar.io

EuroPython 2016, Tobias Oberstein

http://goo.gl/vUUb5e

Tobias Oberstein

  • Twitter/IRC/GH/SO/..: oberstet
  • email: tobias.oberstein@crossbario.com
  • math/statistics background
  • programming since I was 12
  • C/C++, SQL, PL/SQL, JavaScript, Java, Erlang, Assembly, ..
  • preferred language: Python
  • one I want to learn: Rust
  • founder of Tavendo/Crossbar.io
  • we do messaging servers
  • vivid open-source / community supporter
  • commercial open-source business model

Autobahn

points representing the cities the salesman needs to visit

http://autobahn.ws
https://github.com/crossbario/autobahn-python

  • first WebSocket implementation in Python
  • for WebSocket clients/servers
  • for WAMP (Web Application Messaging Protocol) clients
  • supports both Twisted and asyncio on Python 2 and 3
  • used by Mozilla/Firefox (active push), Buildbot 9, Django Channels (Daphne), OpenStack, ..
points representing the cities the salesman needs to visit

http://wamp-proto.org
http://wamp-proto.org/why/

  • open protocol, many implementations, growing ecosystem
  • seamless connectivity for microservices
  • built in Remote Procedure Calls (RPC) and Publish & Subscribe
  • real-time, runs natively over WebSocket
points representing the cities the salesman needs to visit

http://crossbar.io
https://github.com/crossbario/crossbar

  • WAMP router (the best! ;)
  • toolkit for microservice connectivity & deployment
  • open source + commercial support / addons

This presentation is at
http://crossbario.com/static/presentations/microservices

Minimal demo code is at
https://github.com/crossbario/crossbarexamples/tree/master/scaling-microservices

TSP demo code is at
https://github.com/crossbario/crossbarexamples/tree/master/demos/salesman

The what and why of Microservices

Taming Complexity

and avoiding

Taming Complexity: getting there

  • Divide & Conquer
  • One thing, one responsibility
  • Decoupling

Older techs:

  • CORBA
  • DCOM
  • SOAP/WSDL
  • All of above suck BIG time
  • We'll see why REST/HTTP isn't really an answer and why we use WAMP

Scaling Microservices using the Traveling Salesman Problem (TSP) as an example

TSP

points representing the cities the salesman needs to visit

TSP

points representing the cities the salesman needs to visit

TSP

points representing the cities the salesman needs to visit

TSP

11 cities: 39,916,800 combinations

30 cities: 2,652,528,598,121,910,586,363,084,800,000 combinations

TSP is a combinatorial optimization problem in an exponentially large search space

Simulated Annealing

Simulated Annealing

Scaling the TSP

The problem: there are no 1THz cores (because physics)

We could

  • use some tech specialized for parallelization of compute
  • use OpenMP to utilize all cores on single machine
  • use MPI to utilize all machines in a local cluster

or we could use Microservices

A TSP App

app monolith for TSP
app monolith for TSP

A TSP Microservices App

app monolith for TSP

Microservice Interactions - Calls

app monolith for TSP

Microservice Interactions - Distribute Information

app monolith for TSP

So we need

  • Remote Procedure Calls (RPC)
  • Publish & Subscribe (PubSub)

Ideally in one protocol and using one tech.

Web Application Messaging Protocol (WAMP)

  • WAMP (Web Application Messaging Protocol) fits that bill
  • WAMP is a routed protocol, requires a WAMP router
  • Crossbar.io is one WAMP router (ours)

Sessions

app monolith for TSP

Sessions

                  
# this shows Twisted variant, asyncio looks similar
from twisted.internet.defer import inlineCallbacks
from autobahn.twisted.wamp import ApplicationSession

class MySession(ApplicationSession):

    @inlineCallbacks
    def onJoin(self, details):
        self.log.info('session joined: {}'.format(details))

        # "self" is your WAMP session.
        #
        # Your main code lives here:
        #
        # - register and call procedures
        # - subscribe to topics and publish events

        # when you are done, say goodbye!
        yield self.leave()
                  
               

Running Sessions

                  
# start logging
import txaio
txaio.start_logging(level='debug')

# again Twisted, asyncio is similar
from autobahn.twisted.wamp import ApplicationRunner

# use WAMP-over-WebSocket, joining realm "realm1"
runner = ApplicationRunner(u'ws://localhost:8080', u'realm1')

# now connect and run your session
runner.run(MySession, auto_reconnect=True)
                  
               

Publish & Subscribe

app monolith for TSP

Publish & Subscribe

app monolith for TSP

Publish & Subscribe

app monolith for TSP

Publish & Subscribe

app monolith for TSP

Subscribe

                  
def on_hello(msg):
   print("got hello event: {}".format(msg))

try:
    yield session.subscribe(on_hello,
                            u'com.example.on_hello')
except Exception as e:
    # main reason a subscribe may fail: no permission
    print('meh. could not subscribe: {}'.format(e))
                  
               

Publish

                  
session.publish(u'com.example.on_hello', u'Hello, world!')
                  
               
or
                  
try:
    yield session.publish(u'com.example.on_hello',
                          u'Hello, world!',
                          PublishOptions(acknowledge=True))
except Exception as e:
    # main reason a publish may fail: lack of permission
    print('that did not work: {}'.format(e))
                  
               

Routed Remote Procedure Calls

app monolith for TSP

Routed Remote Procedure Calls

app monolith for TSP

Routed Remote Procedure Calls

app monolith for TSP

Routed Remote Procedure Calls

app monolith for TSP

Routed Remote Procedure Calls

app monolith for TSP

Routed Remote Procedure Calls

app monolith for TSP

Routed Remote Procedure Calls

app monolith for TSP

Register

                  
def add2(x, y):
   return x + y

try:
    yield session.register(add2, u'com.example.add2');
except Exception as e:
    # again, lack of permission or "already registered"
    # are main reason for failing to register
    print('you let me down=( {}'.format(e))
                  
               

Call

                  
try:
    result = yield session.call('com.myapp.add2', 2, 3)
    print('got result: {}'.format(result))
except Exception as e:
    # "no such procedure", permissions, wrong arguments, ..
    print('whoa, bad thing: {}'.format(e))
                  
               

Combining actions

  • Call a procedure from an event handler
  • Publish events from a registered procedure
  • Dynamically subcribe/unsubscribe in a procedure
  • Dynamically register/unregister in an event handler
  • ...

Shared Registrations

app monolith for TSP

Shared Registrations - Hot Standby

app monolith for TSP

Shared Registrations - Hot Standby

app monolith for TSP

Shared Registrations - Scale-Out

app monolith for TSP

Shared Registrations - Scale-Out

app monolith for TSP

Shared Registrations - Scale-Out

app monolith for TSP

Shared Registrations

                  
yield session.register(add2,
                       u'com.myapp.add2',
                       {u'invoke': u'roundrobin'});
                  
               

Invocation policies: single, first, last, roundrobin, random

Shared Registrations - Controlling Concurrency

app monolith for TSP

Shared Registrations with Max Concurrency Limits

                  
yield session.register(add2,
                       u'com.myapp.add2',
                       {
                           u'invoke': u'roundrobin',
                           u'concurrency': 4
                       });
                  
               

The TSP App

app monolith for TSP

The TSP App

app monolith for TSP

The TSP App

app monolith for TSP

Summary

  • Microservices: new answer, old problem
  • Connection patterns: RPC + PubSub
  • Crossbar.io + Autobahn make this easy
  • Scaling and hot standby are offered by the router

The Prize Draw

  • win one of 2 IoT Starter Kits
  • take a quick survey
  • < 1 min (more if you want)
  • NO email required

Links