Clustering

EasyRpcServer namepaces can be grouped together with other EasyRpcServer instances, to form "clusters"

Cluster Features:

  • Dynamically Share new / existing functions amongst cluster members
  • Proxy and Reverse proxy functions automatically propogate changes up / downstream every 15 seconds
  • Access to all functions anywhere in a chain

Cluster Example

Server A

# Server A - port 8220
server = FastAPI()
server_a = EasyRpcServer(server, '/ws/server_a', server_secret='abcd1234')

@server_a.origin(namespace='public')
def a_func(a):
    return {'a': a}

Server B


# Server B - port 8221
server = FastAPI()
server_b = EasyRpcServer(
    server, 
    '/ws/server_b', 
    server_secret='abcd1234'
)

@server_b.origin(namespace='public')
def b_func(b):
    return {'b': b}

@server.on_event('startup)
async def setup():
    await server_b.create_server_proxy(
        0.0.0.0, 
        8220, 
        '/ws/server_a', 
        server_secret='abcd1234', 
        namespace='public'
    )

Server C


# Server C - port 8222
server = FastAPI()
server_c = EasyRpcServer(
    server, 
    '/ws/server_c', 
    server_secret='abcd1234'
)

@server_c.origin(namespace='public')
def c_func(c):
    return {'c': c}

@server.on_event('startup)
async def setup():
    await server_c.create_server_proxy(
        '0.0.0.0', 
        8221, 
        '/ws/server_b', 
        server_secret='abcd1234', 
        namespace='public'
    )

Tip

Servers A, B or C can now be accessed via a Proxy to use a_func, b_func, or c_func

Proxy

#client.py
import asyncio
from easyrpc.proxy import EasyRpcProxy

async def main():
    public = await EasyRpcProxy.create(
        '0.0.0.0', 
        8221, 
        '/ws/server_b', 
        server_secret='abcd1234',
        namespace='public'
    )

    await public['a_func']('a')
    await public['b_func']('b')
    await public['c_func']('c')

Cluster Constraints:

Tip

An EasyRpcServer instance may connect up to 1 other EasyRpcServer instance by via a server_proxy per namespace.

The target instance should not be a direct child of the instance connecting(i.e loop)

OK

A->B->C->A

WRONG

A -> B -> A

Tip

An EasyRpcServer can recive n connections from other EasyRpcServer server proxies into a single namespace.

Clustering Patterns

Chaining

A(pub) <-- B(pub) <-- C(pub) <-- D(pub)

Forking

A(pub) <-- B(pub)
A(pub) <-- C(pub)
A(pub) <-- D(pub)

Ring

A(left) <-- B(left) <-- C(left)
A(right) --> C(right) --> B(right)
Creating a ring
A.create_namespace_group('ring', 'left', 'right')
B.create_namespace_group('ring', 'left', 'right')
C.create_namespace_group('ring', 'left', 'right')

Other Considerations

  • Each cluster patterns allow for further forking / chains off the initial nodes of the cluster within the constraints mentioned above.

  • Each namespace-node within the cluster will have access to every other node(namespace) registered functions.

  • The path a node takes to reach a function is relative to where the node registered.

Access

A(pub) <-- B(pub) <-- C(pub) <-- D(pub)

D can access functions on A

D -> C
C -> B
B -> A

Breaks in a Chain

Connection Interuption

D -> C
C # BREAK # B
B -> A

  • C dectects connection is missing, the next proxy probes will remove functions specfic to B & A within the namespace, then propgating the update to D.

  • B dectects connection is missing, the next proxy probes will remove functions specfic to C & D within the namespace, then propgating update A.

Tip

Namespace Groups, discussed next, can help to address these connection interuption concerns.

Namespace Groups

A EasyRpcServer may group two or more namespaces into a single namespace group, providing a single namespace for accessing functions in the group member namespaces.

Features / Considerations:

  • Functions registered to namespace groups automatically register within the member namespaces
  • Namespaces do not allow for duplicate functions, but namespace groups may contain namespaces with same-name functions
  • Namespaces within namespace groups may consist of local / proxy functions
  • Function calls from a namespace group use the first function with the matching name, a duplicates amoungst members are used if the connection to the first function namespace is lost / un-registered.
  • Namespace Group appears like a single Namepsace. If a SERVER proxy connects, all member functions are shared to the connecting Proxy, and all discovered functions are updated in all member namespaces.

Usage

Ring Pattern - Map multiple paths to same functions

Left - A <- B <- C
Right - A -> C -> B
Namespace Group ('ring', 'left', 'right')

Server A
# Server A - port 8220

server = FastAPI()
server_a = EasyRpcServer(server, '/ws/server_a', server_secret='abcd1234')
server_a.create_namespace_group('ring', 'left', 'right')

@server_a.origin(namespace='ring')
def a_func(a):
    return {'a': a}

@server.on_event('startup)
async def setup():
    def delay_proxy_start():
        # sleep to allow other servers to start
        await asyncio.sleep(15)

        await server_a.create_server_proxy(
            0.0.0.0, 8222, '/ws/server_a', server_secret='abcd1234', namespace='right'
        )
    asyncio.create_task(delay_proxy_start())

Server B
# Server B - port 8221
server = FastAPI()
server_b = EasyRpcServer(server, '/ws/server_b', server_secret='abcd1234')
server_b.create_namespace_group('ring', 'left', 'right')

@server_a.origin(namespace='ring')
def b_func(b):
    return {'b': b}

@server.on_event('startup)
async def setup()
    await server_b.create_server_proxy(
        0.0.0.0, 8220, '/ws/server_a', server_secret='abcd1234', namespace='left'
    )
Server C
# Server C - port 8222
server = FastAPI()
server_c = EasyRpcServer(server, '/ws/server_c', server_secret='abcd1234')
server_c.create_namespace_group('ring', 'left', 'right')

@server_a.origin(namespace='ring')
def c_func(c):
    return {'c': c}

@server.on_event('startup)
async def setup()
    await server_c.create_server_proxy(
        0.0.0.0, 8221, '/ws/server_b', server_secret='abcd1234', namespace='left'
    )

All functions in EasyRpcServer A, B, C are registered to both left and right namespaces via ring Namespace Group.

Server A has two paths to functions on Server B & C

A -> C -> B
A -> B -> C

Server B has two paths to functions on Server A & C

B -> A -> C
B -> C -> A

Server C has two paths to functions on Server B & C

C -> B -> A
C -> A -> B

Registering a method to Namespace Group

Simple Grouping and 1 Proxy Connection with single decorator

Server - With Namespace Group
#Public  - A <- B <- C
#Private - A <- D <- E <- F
#Open - A -> G -> H
rpc_server.create_namespace_group('all', 'Public', 'Private', 'Open')
@rpc_server.origin(namespace='all')
def func(a, b, c=10):
    return [a, b, c]

Note

A standard proxy connection provides access to 1 namespace, Namespace Groups can provide two or more namespaces with the same connection.

Proxy - connecting to a Namespace Group
    all_namespaces = await EasyRpcProxy.create(
        '0.0.0.0', 
        8220, 
        '/ws/easy', 
        server_secret='abcd1234',
        namespace='all'
    )