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'
)